Arduinoで遊ぶページ

Arduinoで遊んだ結果を残すページです。
garretlab
GPSアナログ時計

概要

ArduinoにGPSセンサとグラフィック液晶ディスプレイをつないで、高精度アナログ時計(といっても液晶ディスプレイに描画ですが)を作成します。

作るもの

液晶ディスプレイを利用してアナログ時計を作成します。時刻はGPSセンサから取得します。

用意するもの

以下のものを用意します。

  • Arduino
  • USBケーブル
  • PC
  • 電源
  • グラフィック液晶ディスプレイ(SUNLIKE社製SG12864ASLB-GBを利用)
  • 可変抵抗10kΩ(液晶ディスプレイの輝度調整に使います)
  • バックライト用抵抗
  • GPSセンサ(GT-720Fを利用)
  • ブレッドボード(可変抵抗、バックライト用抵抗を配線するために使いました)
  • ジャンパーワイヤ

利用機器

グラフィック液晶ディスプレイ(SG12864ASLB-GB)とGPSセンサ(GT-720F)を利用します。

Arduinoでは、グラフィック液晶ディスプレイを利用するためのライブラリがすでに用意されています。詳細はこちらのページを参照してください。

GPSセンサは、GT-720Fを利用します。GT-720Fを利用した簡単な実験はこちらのページを参照してください。

基本的な考え方

状況把握

GPSセンサから時刻情報(時・分・秒)を取得します。

処理決定

アナログ時計の、時針・分針・秒針の位置を計算します。

機器操作

以前の時針・分針・秒針を消去した後、最新の時針・分針・秒針を描画します。

設計

ハードウェアの設計

グラフィック液晶ディスプレイ(SG12864ASLB-GB)とGPSセンサ(GT-720F)とをArduinoに接続します。ありものを接続するだけです。

プログラムの設計

GPSセンサから時刻の取得

GPSセンサの出力する情報から時刻情報を取得します。今回は、GT-720Fが出力する情報のうち、$GPMRCから時刻情報を取得します。$GPMRCのフォーマットの概略は以下の通りです。$GPRMCの次の情報が時刻情報です。以下の例では、22時50分14秒を表しています。ただし、日本標準時(JST)ではなく協定世界時(UTC)です。このため、JSTにするには、9時間足す必要があります。

$GPRMC,225014.002,A,3500.0000,N,13900.0000,E,000.0,000.0,190811,,,D*6F

文字列から時刻情報を取得するために、sscanf()を利用しました。具体的には、上記に示したGPSセンサの出力を変数(buf)に格納しておき、以下のようにします。hour, minute, secondは、int へのポインタ型の変数です。

sscanf(buf, "$GPRMC,%2d%2d%2d", hour, minute, second);

アナログ時計

グラフィック液晶の初期化

グラフィック液晶を利用する前には、Init()メソッドを利用して初期化する必要があります。書式は以下の通りです。

void glcd::Init(uint8_t invert)

invertは、省略可能で、NON-INVERTEDもしくはINVERTEDを指定します。NON-INVERTEDを指定もしくは省略した場合は、明るい背景に暗いピクセルを描画します。INVERTEDを指定した場合は、その逆で、暗い背景に明るいピクセルを描画します。

初期化はプログラムの開始時に1回行えばいいので、setup()の中で実行します。

時計の枠の描画

時計の枠は単純に円で表現しました。グラフィック液晶を操作するためのglcdライブラリでは、DrawCircle()というメソッドが用意されています。書式は以下の通りです。

void glcd::DrawCircle(uint8_t xCenter, uint8_t yCenter, uint8_t radius, uint8_t color)

xCenter、yCenterは、円の中心の座標、radiusは円の半径です。colorは、省略可能で、BLACKもしくはWHITEを指定可能です。BLACKを指定もしくは省略した場合は円が描画され、WHITEを指定すると白で円を描くので、結果として、円が消えます。

左上が原点で右方向にX軸、下方向にY軸となっている座標系です。SG12864ASLB-GBは、128x64のグラフィック液晶なので、X軸は0から127、Y軸は0から63までの値をとることができます。

半径を指定して円を描きますが、直径は半径x2+1となります。+1は中心の点の分です。

時計の枠は1回描けば十分なので、setup()の中で実行します。最後のプログラムでは、clock.init()の中で実行しています。

針の描画

いろいろな方法があると思いますが、今回は、12時の方向からの角度と針の長さを用いて針を描画します。時計の中心の座標を(center_x, center_y)とし、針の長さをr、12時の方向からの角度をθとすると、

(x, y) = (r sinθ + center_x, - r cosθ + center_y)

と表すことができます。グラフィック液晶の座標系は、数学の座標系と少し違うことに注意が必要です。

また、C言語の三角関数に与える角度はラジアンということにも注意しておく必要があります。180度がπ(C言語の数学ライブラリではM_PIというマクロが定義されています。また、arduinoでは、PIというマクロを利用することもできます)なので、度で表した角度をangle、ラジアンで表した角度をradとすると、以下の数式でラジアンに変換することができます。

rad = angle * PI / 180

針の角度の求め方は、針ごとに異なります。以下にそれぞれの求め方を示します。

時針(短針)

時針は0時のときに0度で、1時間に30度進みます。1時間ごとに30度いきなり進むのもいまいちなので、分と連動させました。1時間で30度なので、1分で0.5度進みます。このため、hour時minute分のときの時針の角度θは以下のように表すことができます。単位は度です。

θ= hour * 30 + minute / 2

UTCをJSTに変換するために時刻に9時間足しています。このため、JSTに変換後の時間は9~33となります。本来であれば、23を超えた場合は24を引くという処理を行う必要がありますが、アナログ時計の場合は25時でも1時でも針の位置は同じなので、今回はこのような処理を行う必要がありません。

分針(長針)

分針は0分のときに0度で、1分間に6度進みます。1分に6度なので、1秒で0.1度進みます。このため、minute分second秒のときの角度θは以下のように表すことができます。単位は度です。

θ = minute * 6 + second / 10
秒針

短針は0秒のときに0度で、1秒間に6度進みます。このため、second秒のときの角度θは以下のように表すことができます。単位は度です。

θ = second * 6

針を書く際は、古い位置の針をすべて消去してから、新しい位置の針を書きました。一つの針を消して書き、次の針を消して書くということをすると、針が重なっていた場合に変なことになります。

glcdライブラリで2点間に直線を引くにはDrawLine()というメソッドを利用します。書式は以下の通りです。

void glcd::DrawLine(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2, uint8_t color)

(x1, y1)と(x2, y2)の間にcolor色の線を引きます。colorはBLACKもしくはWHITEです。colorは省略可能で省略した場合はBLACKを指定したときと同様の動作をします。

今回は素直に三角関数を利用しましたが、いちいち計算はせずに、たとえば、円を4分割して、6度ごとに15個の正弦・余弦をあらかじめ計算して配列に保持するなどしておけば、メモリや計算速度を稼ぐことができるのではと思います。

スケッチ

#include <glcd.h>

const int time_difference = 9;

struct hand {
  int x, y;
  int radius;
};

class analog_clock {
  private:
    struct hand hour_hand;
    struct hand minute_hand;
    struct hand second_hand;
    int center_x, center_y;
    int frame_radius;
    void draw(struct hand *h, int angle);
    void erase(struct hand *h);
  public:
    analog_clock(int x, int y, int h_radius, int m_radius, int s_radius, int f_radius);
    void init();
    void update(int hour, int minute, int second);
};

analog_clock::analog_clock(int x, int y, int h_radius, int m_radius, int s_radius, int f_radius) {
  hour_hand.x = minute_hand.x = second_hand.x = x;
  hour_hand.y = minute_hand.y = second_hand.y = y;
  center_x = x;
  center_y = y;
  hour_hand.radius = h_radius;
  minute_hand.radius = m_radius;
  second_hand.radius = s_radius;
  frame_radius = f_radius;
}

void analog_clock::init() {
    GLCD.DrawCircle(center_x, center_y, frame_radius);
}

void analog_clock::update(int hour, int minute, int second) {
  erase(&hour_hand);
  erase(&minute_hand);
  erase(&second_hand);
  draw(&hour_hand, hour * 30 + minute / 2);
  draw(&minute_hand, minute * 6 + second / 10);
  draw(&second_hand, second * 6);
}

void analog_clock::draw(struct hand *h, int angle) {
  h->x = (float) h->radius * sin(angle * PI / 180) + center_x;
  h->y = - (float) h->radius * cos(angle * PI / 180) + center_y;

  GLCD.DrawLine(center_x, center_y, h->x, h->y);
}

void analog_clock::erase(struct hand *h) {
  GLCD.DrawLine(center_x, center_y, h->x, h->y, WHITE);
}

int gettime(int *hour, int *minute, int *second) {
  char buf[80];
  int pos = 0;
  
  for(;;) {
    if (Serial.available()) {
      buf[pos] = Serial.read();
      if (buf[pos] == '\n') {
        if(sscanf(buf, "$GPRMC,%2d%2d%2d", hour, minute, second) > 0) {
          return 1;
        } else {
          return 0;
        }
      } else {
        pos++;
      }
    }
  }        
}

analog_clock clock(64, 32, 22, 26, 28, 31);

void setup() {
  Serial.begin(9600);
  GLCD.Init();
  clock.init();
}

void loop() {
  int hour, minute, second;

  if(gettime(&hour, &minute, &second)) {
    hour += time_difference;
    clock.update(hour, minute, second);
  }
}

組立

グラフィック液晶ディスプレイの接続はこちらのページを、GPSセンサの接続はこちらのページをそれぞれ参照してください。

その他

GPSで取得した時刻が実際の時刻より1秒程度遅れているように見えます。単に処理に時間がかかりすぎているだけかもしれませんが。

バージョン

Arduino 0022



メニューを表示するためにJavaScriptを有効にしてください。

Arduinoで遊ぶページ
Copyright © 2011 garretlab all rights reserved.
inserted by FC2 system