Single board computer projects

PiAClock: Analog clock on a 64x64 RGB-LED matrix

last updated: 2023-07-24

Quick links

Intro

A very sad matrix display was waiting for 2 years in a box to transform finally in an analog clock.

Hardware

The display has 64x64 RGB LEDs with a 3 mm pitch and can be purchased among others from adafruit: https://www.adafruit.com/product/4732.

We need an adapter. I used the active adapter PCB to support up to 3 panel chains from Henner Zeller (hzeller): https://github.com/hzeller/rpi-rgb-led-matrix/tree/master/adapter/active-3.

BOM

This works with every Raspberry Pi version.

Software

The same Henner Zeller wrote a cool library to use these displays with a Raspi. Everything is explained here: https://github.com/hzeller/rpi-rgb-led-matrix. Clone the repo:

    git clone https://github.com/hzeller/rpi-rgb-led-matrix.git

or download the zip file and extract it.

In the folder examples-api-use you find examples in C++ to test the displays. As I use a Raspi4, I need to set the flag --led-slowdown-gpio=5 to avoid flicker on the display.

Unfortunately there was no analog clock in the examples, so I had to take a closer look at the library and write my own code.

First we need to add some things to the Makefile. We add the program name to the third (Objects=...) and fourth line (BINARIES=...) and add a line after the 33 line (pixel-mover : pixel-mover.o).

    OBJECTS=demo-main.o minimal-example.o ... pixel-mover.o piaclock.o
    BINARIES=demo minimal-example ... pixel-mover piaclock
    ...
    pixel-mover : pixel-mover.o
    piaclock : piaclock

Alternatively you use Makefile from my repo.

Now save the code below as piaclock.cc to the examples-api-use folder.

with make we create the binary file and are then able to run the program:

    cd rpi-rgb-led-matrix/examples-api-use
    sudo make
    sudo ./piaclock --led-slowdown-gpio=5

piaclock.cc

    // -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
    // weigu.lu
    // Small example of an analog clock on a 64x64 matrix.
    // For more examples, look at demo-main.cc
    //
    // This code is public domain
    // (but note, that the led-matrix library this depends on is GPL v2)

    #include "led-matrix.h"
    #include "graphics.h"

    #include <signal.h>
    #include <time.h>
    #include <math.h>

    using namespace rgb_matrix;

    volatile bool interrupt_received = false;
    static void InterruptHandler(int signo) {
      interrupt_received = true;
    }

    int main(int argc, char *argv[]) {
      rgb_matrix::Font font;
      RGBMatrix::Options defaults;
      defaults.hardware_mapping = "regular";  // or e.g. "adafruit-hat"
      defaults.rows = 64; //64x64 matrix
      defaults.cols = 64;
      defaults.chain_length = 1;
      defaults.parallel = 1;
      Canvas *canvas = RGBMatrix::CreateFromFlags(&argc, &argv, &defaults);
      if (canvas == NULL)
        return 1;
      signal(SIGTERM, InterruptHandler);
      signal(SIGINT, InterruptHandler);

      const int x_center = 32;
      const int y_center = 32;

      // set the colors here
      Color sec_color(255, 255, 255);
      Color text_color(255, 50, 50);
      Color min_color(50, 255, 50);
      Color hour_color(50, 50, 255);
      Color bg_color(0, 0, 0);

      const int sec_hand_length = 25;
      const int min_hand_length = 23;
      const int hour_hand_length = 17;
      float dot_x_sec = 0, dot_x_min = 0, dot_x_hour = 0;
      float dot_y_sec = 0, dot_y_min = 0, dot_y_hour = 0;
      float angle = 0;

      const char *bdf_font_file = "../fonts/4x6.bdf";
      font.LoadFont(bdf_font_file);
      int text_xy_arr[12][2] = {
                                {45,10}, // 1 
                                {55,21}, // 2
                                {59,35}, // 3 
                                {55,49}, // 4
                                {45,60}, // 5
                                {31,64}, // 6
                                {16,60}, // 7
                                {6,49},  // 8
                                {3,35},  // 9
                                {2,21},  // 10
                                {12,10}, // 11
                                {29,6}   // 12
                              }; 
      struct tm tm;
      time_t now = time(0); // get time
      localtime_r(&now, &tm);
      int old_sec = tm.tm_sec;
      int old_min = tm.tm_min;
      // draw  everything for the first time
      for (int hour=1; hour<13; hour++) { // draw the 12 hour numbers
        rgb_matrix::DrawText(canvas,font,
                            text_xy_arr[hour-1][0], text_xy_arr[hour-1][1], // x,y
                            text_color, NULL, std::to_string(hour).c_str(),0);
      }
      angle = tm.tm_sec*6 - 90;
      dot_x_sec = cos(angle/360 * 2 * M_PI) * sec_hand_length + 32;
      dot_y_sec = sin(angle/360 * 2 * M_PI) * sec_hand_length + 32;
      rgb_matrix::DrawCircle(canvas,dot_x_sec,dot_y_sec,1,sec_color);
      angle = tm.tm_min*6 - 90;
      dot_x_min = cos(angle/360 * 2 * M_PI) * min_hand_length + 32;
      dot_y_min = sin(angle/360 * 2 * M_PI) * min_hand_length + 32;
      rgb_matrix::DrawLine(canvas,x_center,y_center,dot_x_min,dot_y_min,min_color);
      angle = (tm.tm_hour+(tm.tm_min/60.0))*5*6 - 90;  
      dot_x_hour = cos(angle/360 * 2 * M_PI) * hour_hand_length + 32;
      dot_y_hour = sin(angle/360 * 2 * M_PI) * hour_hand_length + 32;
      rgb_matrix::DrawLine(canvas,x_center,y_center,dot_x_hour,dot_y_hour,hour_color);

      while (!interrupt_received) {
        now = time(0);  // get time
        localtime_r(&now, &tm);
        if (tm.tm_sec != old_sec) {
          old_sec = tm.tm_sec;
          // delete old second hand
          rgb_matrix::DrawCircle(canvas,dot_x_sec,dot_y_sec,1,bg_color);
          // draw new second hand
          angle = tm.tm_sec*6 - 90;
          dot_x_sec = cos(angle/360 * 2 * M_PI) * sec_hand_length + 32;
          dot_y_sec = sin(angle/360 * 2 * M_PI) * sec_hand_length + 32;
          rgb_matrix::DrawCircle(canvas,dot_x_sec,dot_y_sec,1,sec_color);
        }
        if (tm.tm_min != old_min) {
          old_min = tm.tm_min; 
          // delete old min and hour hands
          rgb_matrix::DrawLine(canvas,x_center,y_center,dot_x_min,dot_y_min,bg_color);
          rgb_matrix::DrawLine(canvas,x_center,y_center,dot_x_hour,dot_y_hour,bg_color);
          // draw new min and hour hands
          angle = tm.tm_min*6 - 90;
          dot_x_min = cos(angle/360 * 2 * M_PI) * min_hand_length + 32;
          dot_y_min = sin(angle/360 * 2 * M_PI) * min_hand_length + 32;
          rgb_matrix::DrawLine(canvas,x_center,y_center,dot_x_min,dot_y_min,min_color);
          angle = (tm.tm_hour+(tm.tm_min/60.0))*5*6 - 90;  
          dot_x_hour = cos(angle/360 * 2 * M_PI) * hour_hand_length + 32;
          dot_y_hour = sin(angle/360 * 2 * M_PI) * hour_hand_length + 32;
          rgb_matrix::DrawLine(canvas,x_center,y_center,dot_x_hour,dot_y_hour,hour_color);
        }
      }
      // Animation finished. Shut down the RGB matrix.
      canvas->Clear();
      delete canvas;
      return 0;
    }

Downloads

Interesting links