Friday, August 14, 2020

Alternative displays for HDSP or WiFiChron clocks

So you would like to build your own HDSP clock or WiFiChron clock but you find the HDSP-2534 display too small or too expensive. Why not just replace it with a bigger 8-character display, like the 1-inch 8 x 16-segment or two cascaded Adafruit 0.54" 4 x 14-segment backpacks? We have you covered, in software. Both these displays are now supported, as extension classes of the DAL (Display Abstraction Layer), together with the 128x64 I2C OLED and the HT1632-based 32x8 LED matrix display (used to be from Sure  Electronics). Support could be further extended to LCD displays (think big font on 40x4), MAX6955 with 8 x 16 segment etc., basically anything that can show 8 alphanumeric characters (numbers only, like regular Nixie tubes, would not suffice).

Below are some examples.

Two Adafruit 4-character backpacks are connected on I2C, addresses 0x70 (default) and 0x71, handled by class DisplayAda14seg, which in turn uses the Adafruit_LEDBackpack library.


To enable this display, uncomment the line (in DAL.h, comment out the rest):
#define DISPLAY_ADA_14SEG  // 2 x Adafruit 4x14seg displays;

Functionality for the 8x32 (or 16x32) LED matrix display is implemented in class DisplayHT1632, which is based on code recycled from Wise Clock 4. To enable this display, simply uncomment the line (in DAL.h, comment out the others):
#define DISPLAY_HT1632	 // Sure 8x32 LED matrix controlled by HT1632;



To accommodate the 8 characters on a 32-pixel line, fontTiny had to be used (each character is 4 pixels wide).

Note the connections for the HT1632 display (in DisplayHT1632.cpp):
#define HT1632_DATA 4 // Data pin (pin 7 of display connector)
#define HT1632_CS         3      // Chip Select (pin 1 of display connnector)
#define HT1632_WRCLK 2 // Write clock pin (pin 5 of display connector)
#define HT1632_CLK 5 // clock pin (pin 2 of display connector)

Other possible definitions in DAL.h are:
//****************************************************************************************
// Define the display you wish to use here.
// Comment out the ones not used.

//#define DISPLAY_HDSP2534	// original HDSP-2534 display in HDSP clock;
//#define DISPLAY_HUB08		// TODO: 8x32 LED matrix display with cascading shift registers (?)
//#define DISPLAY_DL1414	// 2 x DL1414; tested June 23, 2018 (http://timewitharduino.blogspot.com/2018/06/wifichron-adapter-for-dl-1414-displays.html)
//#define DISPLAY_HT16K33	// my 8x16-segment driven by HK16K33, tested Jul 26, 2020;
//#define DISPLAY_OLED		// I2C OLED; tested
  #define DISPLAY_HT1632	// Sure 8x32 LED matrix controlled by HT1632; tested Aug 14, 2020;
//#define DISPLAY_MAX6955	// TODO: 8x16 segment driven by MAX6955
//#define DISPLAY_LCD1602	// TODO: classic LCD 16x2 (or better 40x4, with big font);
//#define DISPLAY_ADA_14SEG	// 2 x Adafruit 4x14seg displays, wired on 0x70 and 0x71 I2C addresses; tested Jul 26, 2020;

// show time on the 60-pixel Adafruit Neopixel ring;
//#define _ADD_NEOPIXEL_
//****************************************************************************************

And so on.


Friday, July 31, 2020

HDSP clock fully through-hole revision 3

The only SMD component in the HDSP clock was the USB miniB connector. To make the kit completely beginner-friendly, this connector was replaced by either of its two (right angle or straight) through hole equivalents. Other changes (from revision 2) are:
  • fixed the two smaller pads for DS1307
  • some re-routing (which reduced the number of vias to 5)

The TH right angle USB connector is to be mounted on the top, as shown below.


The straight USB connector can only be mounted on the bottom side of the PCB, as shown below.


This latter configuration allows the clock to be used without any additional enclosure, with just two standoffs holding it in a vertical position, like this:


Assembly instructions for the HDSP clock kit can be found here, with the only difference being the last step, where the miniB USB connector is soldered.



Monday, July 20, 2020

Arduino OLED display shield

The 2.4" I2C OLED display I had sitting idle is too big for the "Promini OLED Clock shield", yet a perfect candidate for a regular Arduino shield. This is how it looks soldered on a prototype shield with two buttons on top, attached to wsduino running the OLED Clock sketch (each of the 5 faces shown):






The current sketch uses U8glib library and takes about 27k of ATmega328's 31k program memory. It could be enhanced by adding alarm (buzzer, relay etc.), since most of the digital pins are available (only D3 and D9 are used for the 2 buttons). Adding NTP time sync (with an ESP8266 module) could also be done, but one/some of the faces will need to be dropped because of memory constraints. All these are exercises/homework for the inquiring minds :)

Note: My nice beveled 2.42" OLED does not seem to be so ubiquitous.


 A quick search on ebay for similar 2.42" OLED displays returns a different style, already mounted on a larger PCB:


This may fit on the Arduino protoshield, but it may look bulky.

Friday, June 19, 2020

HDSP clock with SCD5583 display

Here is another interesting "intelligent" LED matrix display, whose name actually makes some sense (unlike HDSP-2354, among many others): SCD5583A. I reckon it means: "Serial Character Display with 5x5x8 dot matrix". 3 is the code for green (0 for red, 1 for yellow).
This display is now obsolete (replaced by touch screens in avionics, the industry for which it was designed), but it is still available on ebay for a reasonable price (except for incredibly high shipping cost).

Compared to other intelligent displays like the above-mentioned HDSP-2534 employed by the HDSP clock (hence the bland unimaginative name), SCD558X does not have an internally-defined font and requires the user to provide one. This makes it a little more complicated to use, but also allows for more flexibility, making it truly international (non-English character set). Even more, each pixel can be addressed individually, like a graphical 5x40 LED matrix.
The fact that is serial means fewer pins are required for interfacing (3 control pins in this case) and less driving circuitry (no need for the external shift register).

The photo below shows the display connected to an Arduino and running the HDSP sketch adapted for SCD5833.


Practically, the HDSP sketch uses only one function to write to display, aptly called "writeDisplay()", that needs to be re-written. The support for SCD5583 is provided by the class SCD5583, shown below (header + cpp files, compiled with Arduino IDE 1.8.13).

#ifndef _SCD5583_H_
#define _SCD5583_H_

#include  "Arduino.h"

class SCD5583
{
  public:
    SCD5583(byte loadPin, byte dataPin, byte clkPin);
    void clearMatrix();
    void setBrightness(byte b);
    void writeLine(char text[9]);

  private:
    void _sendData(byte data);
    void _getCharDefinition(byte* rows, char c);
    void _selectIndex(byte position);

    byte _loadPin;
    byte _dataPin;
    byte _clkPin;
};

#endif // _SCD5583_H_

-----------------------------------------------
#include "SCD5583.h"
#include "font5x5.h"

#define MAX_CHARS 8   // set to 10 for SCD5510X (10 digits) or to 4 for SCD554X (4 digits);
#define NUM_ROWS  5

SCD5583::SCD5583(byte loadPin, byte dataPin, byte clkPin)
{
  pinMode(loadPin, OUTPUT);
  pinMode(dataPin, OUTPUT);
  pinMode(clkPin, OUTPUT);
  _loadPin = loadPin;
  _dataPin = dataPin;
  _clkPin  = clkPin;
  clearMatrix();
//  setBrightness(0);
}

void SCD5583::clearMatrix()
{
  _sendData(B11000000);
}

void SCD5583::setBrightness(byte b)
{
  if (b > 0 && b < 7)
  {
     _sendData(B11110000 + b);
  }
}

void SCD5583::_selectIndex(byte position)
{
   _sendData(B10100000 + position);
}

void SCD5583::_sendData(byte data)
{
  byte mask = 1;
  digitalWrite(_loadPin, LOW);
  digitalWrite(_clkPin,  LOW);
  for (byte i = 0; i < 8; i++)
  {
    digitalWrite(_dataPin, mask & data? HIGH : LOW);
    digitalWrite(_clkPin, HIGH);
//  delay(10);
    digitalWrite(_clkPin, LOW);
    mask = mask*2;
  }
  digitalWrite(_loadPin, HIGH);
}

void SCD5583::_getCharDefinition(byte* rows, char c)
{
  for (byte i = 0; i < NUM_ROWS; i++)
  {
    rows[i] = font5x5[((c- 0x20) * NUM_ROWS) + i];
  }
}

void SCD5583::writeLine(char* text)
{
  byte rows[NUM_ROWS];
  for (int i = 0; i < MAX_CHARS; i++)
  {
    _selectIndex(i);
    _getCharDefinition(rows, text[i]);
    for (int r = 0; r < NUM_ROWS; r++)
    {
      _sendData(rows[r]);
    }
  }
}

As mentioned previously, the font must be provided too (content of file "font5x5.h" shown below):

#ifndef _FONT5X5_H_
#define _FONT5X5_H_

// ascii characters 0x41-0x7a (32-127);
static unsigned char font5x5[] =
{
  0x00, 0x20, 0x40, 0x60, 0x80, // space
  0x04, 0x24, 0x44, 0x60, 0x84, // !
  0x0A, 0x2A, 0x40, 0x60, 0x80, // "
  0x0A, 0x3F, 0x4A, 0x7F, 0x8A, // #
  0x0F, 0x34, 0x4E, 0x65, 0x9E, // $
  0x19, 0x3A, 0x44, 0x6B, 0x93, // %
  0x08, 0x34, 0x4D, 0x72, 0x8D, // &
  0x04, 0x24, 0x40, 0x60, 0x80, // '
  0x02, 0x24, 0x44, 0x64, 0x82, // (
  0x08, 0x24, 0x44, 0x64, 0x88, // )
  0x15, 0x2E, 0x5F, 0x6E, 0x95, // *
  0x04, 0x24, 0x5F, 0x64, 0x84, // +
  0x00, 0x20, 0x40, 0x64, 0x84, // ,
  0x00, 0x20, 0x4E, 0x60, 0x80, // -
  0x00, 0x20, 0x40, 0x60, 0x84, // .
  0x01, 0x22, 0x44, 0x68, 0x90, // /
  0x0E, 0x33, 0x55, 0x79, 0x8E, // 0
  0x04, 0x2C, 0x44, 0x64, 0x8E, // 1
  0x1E, 0x21, 0x46, 0x68, 0x9F, // 2
  0x1E, 0x21, 0x4E, 0x61, 0x9E, // 3
  0x06, 0x2A, 0x5F, 0x62, 0x82, // 4
  0x1F, 0x30, 0x5E, 0x61, 0x9E, // 5
  0x06, 0x28, 0x5E, 0x71, 0x8E, // 6
  0x1F, 0x22, 0x44, 0x68, 0x88, // 7
  0x0E, 0x31, 0x4E, 0x71, 0x8E, // 8
  0x0E, 0x31, 0x4F, 0x62, 0x8C, // 9
  0x00, 0x24, 0x40, 0x64, 0x80, // :
  0x00, 0x24, 0x40, 0x6C, 0x80, // ;
  0x02, 0x24, 0x48, 0x64, 0x82, // <
  0x00, 0x3F, 0x40, 0x7F, 0x80, // =
  0x08, 0x24, 0x42, 0x64, 0x88, // >
  0x0E, 0x31, 0x42, 0x64, 0x84, // ?
  0x0E, 0x35, 0x57, 0x70, 0x8E, // @
  0x04, 0x2A, 0x5F, 0x71, 0x91, // A
  0x1E, 0x29, 0x4E, 0x69, 0x9E, // B
  0x0F, 0x30, 0x50, 0x70, 0x8F, // C
  0x1E, 0x29, 0x49, 0x69, 0x9E, // D
  0x1F, 0x30, 0x5E, 0x70, 0x9F, // E
  0x1F, 0x30, 0x5E, 0x70, 0x90, // F
  0x0F, 0x30, 0x53, 0x71, 0x8F, // G
  0x11, 0x31, 0x5F, 0x71, 0x91, // H
  0x0E, 0x24, 0x44, 0x64, 0x8E, // I
  0x01, 0x21, 0x41, 0x71, 0x8E, // J
  0x13, 0x34, 0x58, 0x74, 0x93, // K
  0x10, 0x30, 0x50, 0x70, 0x9F, // L
  0x11, 0x3B, 0x55, 0x71, 0x91, // M
  0x11, 0x39, 0x55, 0x73, 0x91, // N
  0x0E, 0x31, 0x51, 0x71, 0x8E, // O
  0x1E, 0x31, 0x5E, 0x70, 0x90, // P
  0x0C, 0x32, 0x56, 0x72, 0x8D, // Q
  0x1E, 0x31, 0x5E, 0x74, 0x92, // R
  0x0F, 0x30, 0x4E, 0x61, 0x9E, // S
  0x1F, 0x24, 0x44, 0x64, 0x84, // T
  0x11, 0x31, 0x51, 0x71, 0x8E, // U
  0x11, 0x31, 0x51, 0x6A, 0x84, // V
  0x11, 0x31, 0x55, 0x7B, 0x91, // W
  0x11, 0x2A, 0x44, 0x6A, 0x91, // X
  0x11, 0x2A, 0x44, 0x64, 0x84, // Y
  0x1F, 0x22, 0x44, 0x68, 0x9F, // Z
  0x07, 0x24, 0x44, 0x64, 0x87, // [
  0x10, 0x28, 0x44, 0x62, 0x81, // \
  0x1C, 0x24, 0x44, 0x64, 0x9C, // ]
  0x04, 0x2A, 0x51, 0x60, 0x80, // ^
  0x00, 0x20, 0x40, 0x60, 0x9F, // _
  0x0A, 0x2A, 0x40, 0x60, 0x80, // '
  0x00, 0x2E, 0x52, 0x72, 0x8D, // a
  0x10, 0x30, 0x5E, 0x71, 0x9E, // b
  0x00, 0x2F, 0x50, 0x70, 0x8F, // c
  0x01, 0x21, 0x4F, 0x71, 0x8F, // d
  0x00, 0x2E, 0x5F, 0x70, 0x8E, // e
  0x04, 0x2A, 0x48, 0x7C, 0x88, // f
  0x00, 0x2F, 0x50, 0x73, 0x8F, // g
  0x10, 0x30, 0x56, 0x79, 0x91, // h
  0x04, 0x20, 0x4C, 0x64, 0x8E, // i
  0x00, 0x26, 0x42, 0x72, 0x8C, // j
  0x10, 0x30, 0x56, 0x78, 0x96, // k
  0x0C, 0x24, 0x44, 0x64, 0x8E, // l
  0x00, 0x2A, 0x55, 0x71, 0x91, // m
  0x00, 0x36, 0x59, 0x71, 0x91, // n
  0x00, 0x2E, 0x51, 0x71, 0x8E, // o
  0x00, 0x3E, 0x51, 0x7E, 0x90, // p
  0x00, 0x2F, 0x51, 0x6F, 0x81, // q
  0x00, 0x33, 0x54, 0x78, 0x90, // r
  0x00, 0x23, 0x44, 0x62, 0x8C, // s
  0x08, 0x3C, 0x48, 0x6A, 0x84, // t
  0x00, 0x32, 0x52, 0x72, 0x8D, // u
  0x00, 0x31, 0x51, 0x6A, 0x84, // v
  0x00, 0x31, 0x55, 0x7B, 0x91, // w
  0x00, 0x32, 0x4C, 0x6C, 0x92, // x
  0x00, 0x31, 0x4A, 0x64, 0x98, // y
  0x00, 0x3E, 0x44, 0x68, 0x9E, // z
  0x06, 0x24, 0x48, 0x64, 0x86, // {
  0x04, 0x24, 0x44, 0x64, 0x84, // |
  0x0C, 0x24, 0x42, 0x64, 0x8C, // }
  0x00, 0x27, 0x5C, 0x60, 0x80, // ~
};

#endif  // _FONT5X5_H_

And finally, the changes in HDSP.ino are:

1. add the following line at the top:

#include "SCD5583.h"

// SCD5583 pins: LOAD to D4, DATA to D5, SDCLK to D3;
SCD5583 scd(4,5,3);

2. replace the function writeDisplay() written for HDSP-2534, with the following:

void writeDisplay()
{
  scd.writeLine(displayBuffer);
}

Hardware-wise, some re-wiring is required on the HDSP board, starting with the removal of the 595 shift register. Then, D3, D4 and D5 (ATmega328 pins 5, 6, 11) must be connected respectively to SDCLK, LOAD and DATA pins (1, 2, 27) of the SCD5583 display.

And there you have it, a working HDSP clock with SCD5583 display.

It is worth mentioning that the measured current drawn was between 10mA (lowest brightness) and 20mA (highest), and that it works similarly well when powered by 3V3.


Wednesday, June 3, 2020

Sure 32x16 bicolor display (HT1632) driven by ESP32

I was recently awoken from the Netflix-infused lethargy by an email from LukeW, who needed help with running the Wise Clock 4 on ESP32. What a great idea that is!

I had an epiphany when I realized what a powerful platform ESP32 is: unbelievable price (about $10 on amazon), serious hardware (memory, speed, WiFi, BT, BLE, multiple USARTs etc.), amazing software support (Arduino IDE, libraries, examples etc.).

The latest Arduino IDE I had was version 1.6.7 (2017). Trying to install the support package for ESP32, I got a security error along the lines:
javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed:
sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target.

It works fine when downloading it through the browser (https://dl.espressif.com/dl/esptool-2.6.1-windows.zip), meaning that the server certificate that esspressif is using is not recognized by the JVM run by Arduino (folder "java" in the arduino directory). Identifying and adding the certificate to the JVM's keystore is time consuming, so I decided that it is just easier to upgrade to the latest Arduino IDE (currently 1.8.12), installed this time (a first for me) from Microsoft store. Even though the process is seamless, I have no idea where this software was placed (definitely not where I wanted it). I also learned the new way (where have I been for so long?) to include and manage libraries (through the "Add .ZIP library" menu item).

Using the ESP32 devkit is as easy as the first Arduino (Duemilanove). The only thing one needs is a solderless breadboard, which I was lucky to have one around, since I don't remember ever using one before. An interesting fact is that the ESP32 board, inserted in the breadboard I have, leaves room for connecting wires only on one side.


Next step was to connect the Sure 32x16 displays (two, daisy chained). I used these pins:

// pins used to connect to ESP32;
#define HT1632_DATA      12 // Data pin (pin 7 of display connector)
#define HT1632_CS            14 // Chip Select (pin 1 of display connector)
#define HT1632_WRCLK  13 // Write clock pin (pin 5 of display connector)
#define HT1632_CLK         27 // clock pin (pin 2 of display connector)

With only the pin changes, the HT1632 files used in Wise Clock 4 software work perfectly fine with ESP32. They can be found here, called from a test sketch that uses 2 displays (#define NUM_DISPLAYS 2 in file MyHT1632.h).

SD card should be next, using the ESP32 support libraries.
With ESP32, the sky is the limit: hopefully no more program memory limitations, no need for third party libraries (e.g. Sanguino), better support for sound, easier access to WiFi, support for extra peripherals etc. Wow!

It did not take long to test the SD card, using this wiring:

which looks like this on the breadboard:


Running SD_Test.ino sketch example coming with ESP32 libraries on a Wise Clock 4 SD card, this is what serial monitor shows:


Incredible, with no sweat at all, just out of the box. Wow again!

Another small step: modified the SD_Test.ino to read the content of the file, line by line:

void setup()
{
    Serial.begin(115200);
    if(!SD.begin()){
        Serial.println("Card Mount Failed");
        return;
    }
    uint8_t cardType = SD.cardType();

    if(cardType == CARD_NONE){
        Serial.println("No SD card attached");
        return;
    }

  openFile(SD, "/quot1.txt");
}

void openFile(fs::FS &fs, const char * path)
{
    Serial.printf("Reading file: %s\n", path);

    file = fs.open(path);
    if(!file){
        Serial.println("Failed to open file for reading");
        return;
    }
}

char lineBuf[500] = {0};

void readLine()
{
  int i = 0;
  lineBuf[0] = 0;

  while (file.available())
  {
    char c = (char) file.read();
    lineBuf[i++] = c;
    if (c == '\n')  break;
  }
  lineBuf[i] = 0;
}

void loop()
{
  readLine();
  Serial.write(lineBuf);
  delay(1000);
}

And the output, in serial monitor: