今から始める電子工作 Step3 ④ Wi-Fi、SPI グラフィック・ディスプレイ -円

 前回、SPIバスのグラフィック・ディスプレイをArduino UNO R4 WiFiに接続し、Wi-Fi経由でNTPで取得した時刻をテキストで表示するところまで確認しました。

 今回、時刻を図形でわかりやすくグラフィック・ディスプレイに表示します。

環境

  • Arduino IDE;2.3.4
  • Windows11;24H2
  • Arduino UNO R4 WiFi 

NTPライブラリで使われているサーバを変更

  C:\Users\ユーザ名\Documents\Arduino\libraries\NTPClient\NTPClient.h

 このNTPライブラリには、pool.ntp.orgというサーバが記入されていました。近いほうがよいと思うので、日本のNTPに変更します。

  const char*   _poolServerName = "ntp.nict.jp; // Default time server

座標

 グラフィック・ディスプレイは、左上が原点です。

 このディスプレイは、90度ごとに表示を回転できるので、筆者の環境で一番見やすい表示にしておきます。

  tft.setRotation(3);

原点の位置と時刻の表示

 座標を確認するスケッチです。

 画面のちらつきを抑えるために、時刻を表示する狭い範囲の背景だけを黒く地の色で塗りつぶしています。

tft.fillRect(0,20,70,20,ST77XX_BLACK);

#include <Adafruit_GFX.h>    // Core graphics library
#include <Adafruit_ST7789.h> // Hardware-specific library for ST7789
#include <SPI.h>
#include <Fonts/FreeSerif9pt7b.h> #define TFT_CS 10 #define TFT_RST 9 // Or set to -1 and connect to Arduino RESET pin #define TFT_DC 8 #include <NTPClient.h>
#include <WiFiS3.h> // for WiFi shield
#include <WiFiUdp.h>  #include "arduino_secrets.h" char ssid[] = SECRET_SSID; // your network SSID (name) char pass[] = SECRET_PASS; // your network password (use for WPA, or use as key for WEP) Adafruit_ST7789 tft = Adafruit_ST7789(TFT_CS, TFT_DC, TFT_RST); WiFiUDP ntpUDP; NTPClient timeClient(ntpUDP); void setup(){ Serial.begin(9600); delay(2000); Serial.println("\nstart Wi-Fi connect"); tft.init(240, 320); // Init ST7789 320x240 Serial.println(F("Initialized TFT")); tft.fillScreen(ST77XX_BLACK); tft.setRotation(3); tft.setFont(&FreeSerif9pt7b); tft.setCursor(0, 0); tft.fillRect(0,0,3,3,ST77XX_WHITE); Serial.println(); WiFi.begin(ssid, pass); while ( WiFi.status() != WL_CONNECTED ) { delay ( 500 ); Serial.print ( "." ); } timeClient.begin(); } void loop() { timeClient.update(); Serial.println(""); Serial.println(timeClient.getFormattedTime()); int H = timeClient.getHours() + 9; if (H > 24 ) H = H - 24; Serial.print(H); int M =timeClient.getMinutes(); Serial.print(":"); Serial.print(M); int S = timeClient.getSeconds(); Serial.print(":"); Serial.println(S); tft.fillRect(0,20,70,20,ST77XX_BLACK); tft.setCursor(3, 35); tft.setTextColor(ST77XX_YELLOW); tft.setTextSize(1); tft.print(H); tft.print(":"); tft.print(timeClient.getMinutes()); tft.print(":"); tft.print(timeClient.getSeconds()); delay(1000); }

 実行例です。文字が小さいです。

 左上の点が原点です。ドットだけでは見えないかもしれないので、3x3の四角形を描画しています。

 tft.fillRect(0,0,3,3,ST77XX_WHITE);
 

円を描画

 時計の描画は、下記のWebページを参照しました。

  rydepier/Arduino-OLED-Clock

 最初に、時計の枠を描画します。ひとつの円だけだと細く感じたので、ずらしながら三つの円を描画しています。最後に、円の中心に時計の軸を描画しました。

  tft.drawCircle(tft.width()/2, tft.height()/2, 118, ST77XX_WHITE);
  tft.drawCircle(tft.width()/2, tft.height()/2, 118-1, ST77XX_YELLOW);
  tft.drawCircle(tft.width()/2, tft.height()/2, 118-2, ST77XX_WHITE);
  tft.drawCircle(tft.width()/2, tft.height()/2, 2, ST77XX_WHITE);

 12個の時間がわかるように印を短い赤色のlineで入れます。ひとつだけだと目立たないので、ずらしてもう一つ描画しました。

float angle;
int X =tft.width()/2;
int Y =tft.height()/2;
  //hour ticks
  for( int z=0; z < 360;z= z + 30 ){
  //Begin at 0° and stop at 360°
    angle = z ;
    angle=(angle/57.29577951) ; //Convert degrees to radians
    int x2=(X+(sin(angle)*116));
    int y2=(Y-(cos(angle)*116));
    int x3=(X+(sin(angle)*(116-10)));
    int y3=(Y-(cos(angle)*(116-10)));
    int x4=(X+(sin(angle+0.5)*116));
    int y4=(Y-(cos(angle+0.5)*116));
    int x5=(X+(sin(angle+0.5)*(116-10)));
    int y5=(Y-(cos(angle+0.5)*(116-10)));
    tft.drawLine(x2,y2,x3,y3,ST77XX_RED);
    tft.drawLine(x4,y4,x5,y5,ST77XX_RED);
  }
 
 実行した様子です。
 

針の描画

 最後に、秒、分、時の針を描画します。描画した後、その座標を記録し、古い座標と新しい座標が一致しないという条件のとき、次の描画の前に黒の地で塗りつぶし、新しい針を描画します。これで、針が動いているように見えます。

 実際に動かすと、45分、15分付近に針の描画が残ることがありました。シリアルモニタにパラメタを記録していくと、cos(1)のときに、古い座標と新しい座標が一致してしまい、黒の地で塗りつぶす記述をスキップすることがわかりました。

 下記は秒の記述のところですが、yの角度がほぼ0になるとき、実際は0と約12でスキップしているようですが、24.2以下のときに、黒の地を描画する記述を追加しました。

  if (abs(cos(angle)*(116)) < 24.2) {
    tft.drawLine(X,Y,x31,y31,ST77XX_BLACK);
  }
 

#include <Adafruit_GFX.h>    // Core graphics library
#include <Adafruit_ST7789.h> // Hardware-specific library for ST7789
#include <SPI.h>
#include <Fonts/FreeSerif9pt7b.h> #define TFT_CS 10 #define TFT_RST 9 // Or set to -1 and connect to Arduino RESET pin #define TFT_DC 8 #include <NTPClient.h>
#include <WiFiS3.h> // for WiFi shield
#include <WiFiUdp.h> #include "arduino_secrets.h" char ssid[] = SECRET_SSID; // your network SSID (name) char pass[] = SECRET_PASS; // your network password (use for WPA, or use as key for WEP) Adafruit_ST7789 tft = Adafruit_ST7789(TFT_CS, TFT_DC, TFT_RST); WiFiUDP ntpUDP; NTPClient timeClient(ntpUDP); int x10,y10,x20,y20,x30,y30; int x11,y11,x21,y21,x31,y31; void setup(){ Serial.begin(9600); delay(2000); Serial.println("\nstart Wi-Fi connect"); tft.init(240, 320); // Init ST7789 320x240 Serial.println(F("Initialized TFT")); tft.fillScreen(ST77XX_BLACK); tft.setRotation(3); tft.setFont(&FreeSerif9pt7b); tft.setCursor(0, 0); tft.fillRect(0,0,3,3,ST77XX_WHITE); Serial.println(); WiFi.begin(ssid, pass); while ( WiFi.status() != WL_CONNECTED ) { delay ( 500 ); Serial.print ( "." ); } timeClient.begin(); } void loop() { timeClient.update(); Serial.println(""); Serial.println(timeClient.getFormattedTime()); int H = timeClient.getHours() + 9; if (H > 24 ) H = H - 24; Serial.print(H); int M =timeClient.getMinutes(); Serial.print(":"); Serial.print(M); int S = timeClient.getSeconds(); Serial.print(":"); Serial.println(S); tft.fillRect(0,20,70,20,ST77XX_BLACK); tft.setCursor(3, 35); tft.setTextColor(ST77XX_YELLOW); tft.setTextSize(1); tft.print(H); tft.print(":"); tft.print(timeClient.getMinutes()); tft.print(":"); tft.print(timeClient.getSeconds()); tft.drawCircle(tft.width()/2, tft.height()/2, 118, ST77XX_WHITE); tft.drawCircle(tft.width()/2, tft.height()/2, 118-1, ST77XX_YELLOW); tft.drawCircle(tft.width()/2, tft.height()/2, 118-2, ST77XX_WHITE); tft.drawCircle(tft.width()/2, tft.height()/2, 2, ST77XX_WHITE); float angle; int X =tft.width()/2; int Y =tft.height()/2; //hour ticks for( int z=0; z < 360;z= z + 30 ){ //Begin at 0° and stop at 360° angle = z ; angle=(angle/57.29577951) ; //Convert degrees to radians int x2=(X+(sin(angle)*116)); int y2=(Y-(cos(angle)*116)); int x3=(X+(sin(angle)*(116-10))); int y3=(Y-(cos(angle)*(116-10))); int x4=(X+(sin(angle+0.5)*116)); int y4=(Y-(cos(angle+0.5)*116)); int x5=(X+(sin(angle+0.5)*(116-10))); int y5=(Y-(cos(angle+0.5)*(116-10))); tft.drawLine(x2,y2,x3,y3,ST77XX_RED); tft.drawLine(x4,y4,x5,y5,ST77XX_RED); } // display second hand angle = S * 6.0 ; angle=(angle/57.29577951) ; //Convert degrees to radians x30=(X+(sin(angle)*(116))); y30=(Y-(cos(angle)*(116))); if (x30 != x31) { tft.drawLine(X,Y,x31,y31,ST77XX_BLACK); } if (abs(cos(angle)*(116)) < 24.2) { tft.drawLine(X,Y,x31,y31,ST77XX_BLACK); } tft.drawLine(X,Y,x30,y30,ST77XX_GREEN); x31=x30; y31=y30; // display minute hand angle = M * 6 ; angle=(angle/57.29577951) ; //Convert degrees to radians x20=(X+(sin(angle)*(116-15))); y20=(Y-(cos(angle)*(116-15))); if (x20 != x21) { tft.drawLine(X,Y,x21,y21,ST77XX_BLACK); } if (abs(cos(angle)*(116-15)) < 24.2) { tft.drawLine(X,Y,x21,y21,ST77XX_BLACK); } tft.drawLine(X,Y,x20,y20,ST77XX_WHITE); x21=x20; y21=y20; // display hour hand angle = H * 30 + int((M / 12) * 6 ) ; angle=(angle/57.29577951) ; //Convert degrees to radians x10=(X+(sin(angle)*(116-42))); y10=(Y-(cos(angle)*(116-42))); if (x10 != x11) { tft.drawLine(X,Y,x11,y11,ST77XX_BLACK); } if (abs(cos(angle)*(116-42)) < 24.2) { tft.drawLine(X,Y,x11,y11,ST77XX_BLACK); } tft.drawLine(X,Y,x10,y10,ST77XX_WHITE); x11=x10; y11=y10; delay(1000); }

 実行している様子です。 秒針は緑色、分と時は白色ですが、長さを変えています。

改良1

 針が細いlineで描画しているだけなので、わかりにくいです。

 まず、秒針から修正します。先端に輪っかを描画します。

    tft.drawLine(X,Y,x30,y30,ST77XX_GREEN);
    tft.drawCircle(x30s, y30s, 7, ST77XX_GREEN);
 

 

改良2

 loop()のさいごでは、1秒待ち時間を入れていました。

  delay(1000);
}
 
 これでは、loop()内の処理時間が加わるので、1秒を超えてしました。描画している時計を見ていると、時々2秒進みます。
 正確に1000msでループが回るように修正します。
 
void loop() {
  unsigned long time0, time;
  time0 = millis();
 
 。。。
 
  time = millis();
  while ((time - time0)<1000){  // 1000ms
    delay(1); time = millis();
  }
}
 

 実測でほぼ1000msのループになりました。

改良3

 分、時の針も修正します。

#include <Adafruit_GFX.h>    // Core graphics library
#include <Adafruit_ST7789.h> // Hardware-specific library for ST7789
#include <SPI.h>
#include <Fonts/FreeSerif9pt7b.h> #define TFT_CS 10 #define TFT_RST 9 // Or set to -1 and connect to Arduino RESET pin #define TFT_DC 8 #include <NTPClient.h>
#include <WiFiS3.h> // for WiFi shield
#include <WiFiUdp.h> #include "arduino_secrets.h" char ssid[] = SECRET_SSID; // your network SSID (name) char pass[] = SECRET_PASS; // your network password (use for WPA, or use as key for WEP) Adafruit_ST7789 tft = Adafruit_ST7789(TFT_CS, TFT_DC, TFT_RST); WiFiUDP ntpUDP; NTPClient timeClient(ntpUDP); int x10,y10,x10s,y10s,x10ss,y10ss,x10sss,y10sss,x10ssss,y10ssss; int x20,y20,x20s,y20s,x20ss,y20ss,x30,y30,x30s,y30s; int x11,y11,x11s,y11s,x11ss,y11ss,x11sss,y11sss,x11ssss,y11ssss; int x21,y21,x21s,y21s,x21ss,y21ss,x31,y31,x31s,y31s; void setup(){ Serial.begin(9600); delay(2000); Serial.println("\nstart Wi-Fi connect"); tft.init(240, 320); // Init ST7789 320x240 Serial.println(F("Initialized TFT")); tft.fillScreen(ST77XX_BLACK); tft.setRotation(3); tft.setFont(&FreeSerif9pt7b); tft.setCursor(0, 0); tft.fillRect(0,0,3,3,ST77XX_WHITE); Serial.println(); WiFi.begin(ssid, pass); while ( WiFi.status() != WL_CONNECTED ) { delay ( 500 ); Serial.print ( "." ); } timeClient.begin(); timeClient.update(); } void loop() { unsigned long time0, time; time0 = millis(); timeClient.update(); Serial.println(""); Serial.println(timeClient.getFormattedTime()); int H = timeClient.getHours() + 9; if (H > 24 ) H = H - 24; Serial.print(H); int M =timeClient.getMinutes(); Serial.print(":"); Serial.print(M); int S = timeClient.getSeconds(); Serial.print(":"); Serial.println(S); tft.fillRect(0,20,70,20,ST77XX_BLACK); tft.setCursor(3, 35); tft.setTextColor(ST77XX_YELLOW); tft.setTextSize(1); tft.print(H); tft.print(":"); tft.print(M); tft.print(":"); tft.print(S); tft.drawCircle(tft.width()/2, tft.height()/2, 118, ST77XX_WHITE); tft.drawCircle(tft.width()/2, tft.height()/2, 118-1, ST77XX_YELLOW); tft.drawCircle(tft.width()/2, tft.height()/2, 118-2, ST77XX_WHITE); tft.drawCircle(tft.width()/2, tft.height()/2, 2, ST77XX_WHITE); float angle; int X =tft.width()/2; int Y =tft.height()/2; //hour ticks for( int z= 0; z < 360; z = z + 30 ){ //Begin at 0° and stop at 360° angle = z ; angle=(angle/57.29577951) ; //Convert degrees to radians int x2=(X+(sin(angle)*116)); int y2=(Y-(cos(angle)*116)); int x3=(X+(sin(angle)*(116-10))); int y3=(Y-(cos(angle)*(116-10))); int x4=(X+(sin(angle+0.5)*116)); int y4=(Y-(cos(angle+0.5)*116)); int x5=(X+(sin(angle+0.5)*(116-10))); int y5=(Y-(cos(angle+0.5)*(116-10))); tft.drawLine(x2,y2,x3,y3,ST77XX_RED); tft.drawLine(x4,y4,x5,y5,ST77XX_RED); } // display second hand angle = S * 6.0 ; angle=(angle/57.29577951) ; //Convert degrees to radians x30=(X+(sin(angle)*(116))); y30=(Y-(cos(angle)*(116))); x30s=(X+(sin(angle)*(116-20))); y30s=(Y-(cos(angle)*(116-20))); if (x30 != x31) { tft.drawLine(X,Y,x31,y31,ST77XX_BLACK); tft.drawCircle(x31s, y31s, 7, ST77XX_BLACK); } if (abs(cos(angle)*(116)) < 24.2) { tft.drawLine(X,Y,x31,y31,ST77XX_BLACK); tft.drawCircle(x31s, y31s, 7, ST77XX_BLACK); } tft.drawLine(X,Y,x30,y30,ST77XX_GREEN); tft.drawCircle(x30s, y30s, 7, ST77XX_GREEN); x31=x30;x31s=x30s; y31=y30;y31s=y30s; // display minute hand angle = M * 6 ; angle=(angle/57.29577951) ; //Convert degrees to radians x20=(X+(sin(angle)*(116-15))); y20=(Y-(cos(angle)*(116-15))); x20s=(X+(sin(angle+0.02)*(116-26))); y20s=(Y-(cos(angle+0.02)*(116-26))); x20ss=(X+(sin(angle-0.02)*(116-26))); y20ss=(Y-(cos(angle-0.02)*(116-26))); if (x20 != x21) { tft.drawLine(X,Y,x21,y21,ST77XX_BLACK); tft.drawLine(X+1,Y+1,x21s,y21s,ST77XX_BLACK); tft.drawLine(X-1,Y-1,x21ss,y21ss,ST77XX_BLACK); tft.drawLine(x21,y21,x21s,y21s,ST77XX_BLACK); tft.drawLine(x21,y21,x21ss,y21ss,ST77XX_BLACK); } if (abs(cos(angle)*(116-15)) < 24.2) { tft.drawLine(X,Y,x21,y21,ST77XX_BLACK); tft.drawLine(X+1,Y+1,x21s,y21s,ST77XX_BLACK); tft.drawLine(X-1,Y-1,x21ss,y21ss,ST77XX_BLACK); tft.drawLine(x21,y21,x21s,y21s,ST77XX_BLACK); tft.drawLine(x21,y21,x21ss,y21ss,ST77XX_BLACK); } tft.drawLine(X,Y,x20,y20,ST77XX_WHITE); tft.drawLine(X+1,Y+1,x20s,y20s,ST77XX_WHITE); tft.drawLine(X-1,Y-1,x20ss,y20ss,ST77XX_WHITE); tft.drawLine(x20,y20,x20s,y20s,ST77XX_WHITE); tft.drawLine(x20,y20,x20ss,y20ss,ST77XX_WHITE); x21=x20;x21s=x20s;x21ss=x20ss; y21=y20;y21s=y20s;y21ss=y20ss; // display hour hand angle = H * 30 + int((M / 12) * 6 ) ; angle=(angle/57.29577951) ; //Convert degrees to radians x10=(X+(sin(angle)*(116-40))); y10=(Y-(cos(angle)*(116-40))); x10s=(X+(sin(angle+0)*(116-42))); y10s=(Y-(cos(angle+0)*(116-42))); x10ss=(X+(sin(angle-0.2)*(116-70))); y10ss=(Y-(cos(angle-0.2)*(116-70))); x10sss=(X+(sin(angle+0.2)*(116-70))); y10sss=(Y-(cos(angle+0.2)*(116-70))); x10ssss=(X+(sin(angle)*(116-105))); y10ssss=(Y-(cos(angle)*(116-105))); if (x10 != x11) { tft.drawLine(X,Y,x11,y11,ST77XX_BLACK); tft.fillTriangle(x11s, y11s, x11ss, y11ss, x11sss, y11sss, ST77XX_BLACK); tft.fillTriangle(x11ssss, y11ssss, x11ss, y11ss, x11sss, y11sss, ST77XX_BLACK); } if (abs(cos(angle)*(116-42)) < 24.2) { tft.drawLine(X,Y,x11,y11,ST77XX_BLACK); tft.fillTriangle(x11s, y11s, x11ss, y11ss, x11sss, y11sss, ST77XX_BLACK); tft.fillTriangle(x11ssss, y11ssss, x11ss, y11ss, x11sss, y11sss, ST77XX_BLACK); } tft.drawLine(X,Y,x10,y10,ST77XX_WHITE); tft.fillTriangle(x10s, y10s, x10ss, y10ss, x10sss, y10sss, 0x7777); tft.fillTriangle(x10ssss, y10ssss, x10ss, y10ss, x10sss, y10sss, 0x5555); x11=x10;x11s=x10s;x11ss=x10ss;x11sss=x10sss;x11ssss=x10ssss; y11=y10;y11s=y10s;y11ss=y10ss;y11sss=y10sss;y11ssss=y10ssss; time = millis(); while ((time - time0)<1000){ // 1000ms delay(1); time = millis(); } }

 実行中の様子です。

前へ

今から始める電子工作 Step3 ③ Wi-Fi、SPI グラフィック・ディスプレイ -テキスト