ちょっと応用
ここまでのおさらいとして、Arduino UNO R4 WiFiで搭載されたWiFi機能、RTC機能、LEDマトリクスをすべて使って、デジタル時計を作ってみました。
プログラム
#include "Arduino_LED_Matrix.h"
#include "WiFiS3.h"
#include "RTC.h"
#include "arduino_secrets.h"
char ssid[] = _SSID;
char pass[] = _PASS;
// NTPアクセス関係
//char ntpServer[] = "pool.ntp.org";
IPAddress ntpServer(192,168,0,22);
int ntpPort = 123;
// 送受信用バッファの用意
const int NTP_PACKET_SIZE = 48;
byte buffer[NTP_PACKET_SIZE];
// クライアントのインスタンスの生成
WiFiUDP client;
// LEDマトリクスのインスタンスの生成
ArduinoLEDMatrix matrix;
// 表示のためのパターン
byte frame[8][12];
byte charPattern[10][5][3]={
{ {0,1,0}, {1,0,1}, {1,0,1}, {1,0,1}, {0,1,0} }, //0
{ {0,1,0}, {0,1,0}, {0,1,0}, {0,1,0}, {0,1,0} }, //1
{ {1,1,1}, {0,0,1}, {1,1,1}, {1,0,0}, {1,1,1} }, //2
{ {1,1,1}, {0,0,1}, {1,1,1}, {0,0,1}, {1,1,1} }, //3
{ {1,0,1}, {1,0,1}, {1,1,1}, {0,0,1}, {0,0,1} }, //4
{ {1,1,1}, {1,0,0}, {1,1,1}, {0,0,1}, {1,1,1} }, //5
{ {1,0,0}, {1,0,0}, {1,1,1}, {1,0,1}, {1,1,1} }, //6
{ {1,1,1}, {0,0,1}, {0,1,0}, {0,1,0}, {0,1,0} }, //7
{ {1,1,1}, {1,0,1}, {1,1,1}, {1,0,1}, {1,1,1} }, //8
{ {1,1,1}, {1,0,1}, {1,1,1}, {0,0,1}, {0,0,1} } //9
};
void setup() {
Serial.begin(9600);
delay(1000);
// WiFiモジュールの存在確認
if(WiFi.status() == WL_NO_MODULE){
Serial.println("WiFiモジュールがありません");
while(true);
}
// WiFiファームウェアバージョンの確認
String fv = WiFi.firmwareVersion();
if(fv < WIFI_FIRMWARE_LATEST_VERSION){
Serial.println("ファームウェアが最新のものではありません");
}
// 無線LANへの接続
Serial.print("接続中...");
while(true){
if(WiFi.begin(ssid, pass) == WL_CONNECTED)break;
Serial.print(".");
delay(1000);
}
Serial.println("完了");
// Arduinoに割り当てられたIPアドレスの確認
Serial.print("IPアドレス:");
Serial.println(WiFi.localIP().toString());
// UDPポートを開く
client.begin(ntpPort);
// NTPサーバに対してリクエストを送信
memset(buffer, 0, NTP_PACKET_SIZE);
buffer[0] = 0b00001011;
client.beginPacket(ntpServer, ntpPort);
client.write(buffer, NTP_PACKET_SIZE);
client.endPacket();
Serial.println("リクエストを送信");
// NTPサーバからのレスポンスを受信
unsigned long transTime;
while(true){
if(client.parsePacket()){
client.read(buffer, NTP_PACKET_SIZE);
// 時刻(Transmit Timestamp)の取得
transTime = buffer[40]<<24 | buffer[41]<<16 | buffer[42]<<8 | buffer[43];
break;
}
}
Serial.println("時刻を受信");
// RTC開始と時刻の設定
RTC.begin();
unsigned long unixTime = transTime - 2208988800L + 32400L;
RTCTime time(unixTime);
RTC.setTime(time);
// 表示マトリクスの初期化
matrix.begin();
}
void putCharacter(int x0, int y0, int ch){
for(int y=0;y<5;y++){
for(int x=0;x<3;x++){
frame[y0+y][x0+x]=charPattern[ch][y][x];
}
}
}
void loop() {
RTCTime currentTime;
RTC.getTime(currentTime);
int hh = currentTime.getHour();
int mm = currentTime.getMinutes();
int ss = currentTime.getSeconds();
memset(frame,0,96);
putCharacter(0,1,hh/10);
putCharacter(3,1,hh%10);
putCharacter(6,1,mm/10);
putCharacter(9,1,mm%10);
for(int i=0;i<ss/5;i++){
frame[7][i]=1;
}
if(ss%2==0){
frame[7][ss/5]=1;
}
matrix.renderBitmap(frame,8,12);
char tmp[10];
sprintf(tmp,"%02d:%02d:%02d",hh,mm,ss);
Serial.println(tmp);
delay(1000);
}
プログラムの解説
このプログラムでは、開始時にネットワークに接続して自動的にNTPサーバから現在時刻を取得します。それ以降の時刻のカウントは内蔵のRTC機能を使用しています。
ネットワークに接続してNTPサーバから時刻を取得する部分は以前のプログラムと同じなので、新しい部分のみ解説します。
NTPサーバの指定
// NTPアクセス関係
//char ntpServer[] = "pool.ntp.org";
IPAddress ntpServer(192,168,0,22);
int ntpPort = 123;
以前からある部分ですが補足です。接続先のNTPサーバを指定するグローバル変数 ntpServer は char[]型の文字列でサーバ名(FQDN)を指定するか、または IPAddress型でIPv4アドレスを指定します。実際にデータの送信先を指定する beginPacket()関数が引数がchar[]型でもIPAddress型でも動作する(オーバーロード定義されている)ので、NTPサーバの変更はこの行の変更だけですみます。
ちなみに 192.168.0.22 というのはブログ主の実験環境(LAN)内のWindows PCです。以前の記事『Windows PCをNTPサーバにする』のやり方で実験用サーバにしています。
文字パターンの定義
// 表示のためのパターン
byte frame[8][12];
byte charPattern[10][5][3]={
{ {0,1,0}, {1,0,1}, {1,0,1}, {1,0,1}, {0,1,0} }, //0
{ {0,1,0}, {0,1,0}, {0,1,0}, {0,1,0}, {0,1,0} }, //1
{ {1,1,1}, {0,0,1}, {1,1,1}, {1,0,0}, {1,1,1} }, //2
{ {1,1,1}, {0,0,1}, {1,1,1}, {0,0,1}, {1,1,1} }, //3
{ {1,0,1}, {1,0,1}, {1,1,1}, {0,0,1}, {0,0,1} }, //4
{ {1,1,1}, {1,0,0}, {1,1,1}, {0,0,1}, {1,1,1} }, //5
{ {1,0,0}, {1,0,0}, {1,1,1}, {1,0,1}, {1,1,1} }, //6
{ {1,1,1}, {0,0,1}, {0,1,0}, {0,1,0}, {0,1,0} }, //7
{ {1,1,1}, {1,0,1}, {1,1,1}, {1,0,1}, {1,1,1} }, //8
{ {1,1,1}, {1,0,1}, {1,1,1}, {0,0,1}, {0,0,1} } //9
}
変数 frame[][] はLEDマトリクス全体の点滅情報を格納する配列です。今回は一度に1画面分のデータしか使わないので、メモリ効率は悪いですが直感的に判りやすいように12×8の配列を用意しました。
変数 charPettern[][][] は数字の点滅パターン(フォントデータ)です。1文字3×5のデータです。
文字を描画する
void putCharacter(int x0, int y0, int ch){
for(int y=0;y<5;y++){
for(int x=0;x<3;x++){
frame[y0+y][x0+x]=charPattern[ch][y][x];
}
}
}
数字を1つ描画します。引数x0、y0が表示位置、chが表示する数字を表します。
動作は単純で、LEDの点滅情報を charPattern[][][] から frame[][] の該当位置にコピーしているだけです。
loop()関数
void loop() {
RTCTime currentTime;
RTC.getTime(currentTime);
まず、RTC.getTime(currentTime) で変数currentTimeに現在時刻を取得します。
int hh = currentTime.getHour();
int mm = currentTime.getMinutes();
int ss = currentTime.getSeconds();
変数currentTimeから、getHour()関数・getMinutes()関数・getSeconds()関数でそれぞれ時・分・秒を取得します。
memset(frame,0,96);
putCharacter(0,1,hh/10);
putCharacter(3,1,hh%10);
putCharacter(6,1,mm/10);
putCharacter(9,1,mm%10);
memset()関数は変数frame[][]の要素の値をすべて0にしています。画面クリアに相当します。
先に定義したputCharacter()関数で数字を1文字ずつ描画します。
hh/10は『時』の十位
hh%10は『時』の一の位
mm/10は『分』の十位
mm%10は『分』の一の位
を計算しています。
for(int i=0;i<ss/5;i++){
frame[7][i]=1;
}
if(ss%2==0){
frame[7][ss/5]=1;
}
この部分では、画面下のバーを描画しています。1つめのfor文がバー本体、2つめのfor文が点滅しているバーの先端部分を描画しています。
matrix.renderBitmap(frame,8,12);
renderBitmap()関数で変数frame内の点滅情報を実際のLEDマトリクスに表示します。
char tmp[10];
sprintf(tmp,"%02d:%02d:%02d",hh,mm,ss);
Serial.println(tmp);
delay(1000);
}
この部分では、動作確認用に時刻を『hh:mm:ss』の形式でシリアル送信しています。
実行結果
LEDマトリクスは横に12ピクセル、数字を表示するにはどうしても幅3ピクセルは必要なので、文字同士がくっついてしまって読みにくいですが、それでも時刻がちゃんと表示されています。
一番下のバーは秒針代わりです。12ピクセルなので5秒に1ピクセルずつ伸びていきます。
まとめ
今回はArduino UNO R4 WiFi の独自機能を一通り使用する簡単なサンプルとして、『NTPから自動的に時刻を取得する時計』を作ってみました。さらに定期的にNTPサーバから時刻を取得して正確さを保つ、『Arduinoで音を出す』を参考にしてアラーム機能を付けるなどしても面白いかもしれません。
コメント