Thursday, July 17, 2014

New kit in store: simple clock with HDSP-2534 display

The centerpiece of this clock kit is the vintage-style 8-character display HDSP-2534 originally from HP, currently manufactured by Avago. The assembled clock looks like in the photo below. The dock, not included in the kit, is a miniB USB phone charger; it can be easily sourced from ebay, if you don't already have one. (You can even get a fancy one, e.g. custom-made exotic wood, on etsy.com.)


The kit includes the following components:

  • PCB;
  • ATmega328P, with bootloader and fuses for 8MHz internal clock, and the sketch preloaded (also downloadable from here);
  • HDSP-2534 display;
  • 595 shift register;
  • DS1307 real time clock;
  • 32kHz crystal
  • CR1220 battery;
  • battery holder;
  • push buttons (x2);
  • capacitor 100nF (x3);
  • 10k resistor (x3);
  • 28-pin socket;
  • 16-pin socket;
  • 8-pin socket;
  • 6-pin machined female header (x4).

   $40, free shipping to North America

Note: The kit is currently out of stock. Please send me an email (my address is in the top right corner of the page) if you want one. I will put together only a small number of kits at this price, since the display itself is sold by digikey for about $40.



Schematic and board layout are shown below. Preliminary Eagle files can be found here.



The kit is super easy to assemble. It is really impossible to misplace components on the board.
Still, here is some advice:
  • pay attention to the orientation of each of the three integrated circuits, when you insert them in their respective sockets;
  • before soldering the battery holder, put some solder on the big center pad;
  • avoid solder bridges between the USB miniB connector's terminals by wetting their pads (on the bottom side) with a flux pen;
  • it is recommended, for aesthetic purpose, not to solder the FTDI connector to the board; if you need to upgrade the existing software (download from here), you can just hold the 6-pin make header tightly in place while uploading the sketch;


Thursday, July 10, 2014

bGeigie Nano PCB remixed

As I pointed out in my review on Safecast bGeigie Nano kit, the size of the current software already reached the limit of processor's program memory of 30KB or so. From this point on, it is difficult (if not impossible) to add new code features, and that may require drastic code optimization or even disabling existing features.

I thought that the easiest solution to keep this project up-to-date is by upgrading the processor, by entirely replacing the Fio board with an ATmega 644P plus a few extra components (e.g. LiPo charger). This makes the kit a bit more challenging to build, since it requires soldering SMD components, but could also save a few bucks. The device is still Arduino-compatible and programmable, like to Fio, using the FTDI breakout.

Updated Jul 11, 2014: As Rob suggested, I added the XBee module as well. New Eagle files are here.
This is how the bGeigie Nano Plus PCB looks now. (I also added an option for the Fastrax UP501 module, a bit cheaper then the Ultimate GPS breakout, but still a great GPS module (if you can find it on ebay or other sellers)).


The Eagle files for the upgraded bGeigie Nano can be found here.
On the board, the positioning of the modules, switches, headers etc. is the same (except for the Fio, which is now gone).


A few more points:
  • The LiPo charger follows Fio's schematic, using the exact same components.
  • The ATmega644P processor will require burning the bootloader (board has ICSP header).
  • To compile and upload sketches, the Sanguino core files need to be added to the Arduino IDE (as detailed in this post).
  • ATmega1284P, with double the program memory, could be used instead of the 644P, since it has the exact same footprint (and it is pin compatible).


Unfortunately I am short on cash these days, so I hope someone could order the PCB with oshpark (the set of 3 original PCBs was $52 when I ordered them a while ago) and prove the design. I could help with the IDE setup and probably with some software development as well (the first thing that comes to mind is using the whole 128x64 OLED screen, and not just half).

As always, any feedback is appreciated.

Sunday, July 6, 2014

July 2014 release of Wise Clock 4 software

This latest code release includes:
  1. introduction of the two-faced (Kandinsky) feature, on 2 or 4 displays;
  2. fixes related to screen centering for 3 or 4 displays (Big, Stopwatch etc);
  3. fix for Pacman app on 3/4 displays;
  4. fix for Words app on 3/4 displays (so the text does not start scrolling in the middle of the screen);
  5. improved the font definition for digits (effectively making them 5x7 pixels; see photo below);
  6. fix for Score app (swapped button functions);


I am sure that there are still a few bugs to fix.
As well, some improvements would be nice, the most notable being the better use of a screen with 3 or 4 displays, especially for the Big mode, where the time is still shown on the (center) 2 displays. (This would call for spreading out of the digits, or even the definition of a wider font.)


(One of the displays in the 3-display clock is faulty, hence the missing pixel in the bottom 1.)

Sunday, June 22, 2014

Wise Clock chronometer completed

As I briefly mentioned in my previous post, I re-started working on the Wise Clock chronometer. That old version only displayed the time from the RTC, and lacked basic user functions like setting the time from buttons. Even though it was called "chronometer" it did not have a proper timer functionality (besides showing the time with seconds).

I implemented the new software(*) (available here) as a state machine, easy to describe, understand and upgrade. Below is the diagram with the states and transitions.


This clock uses the same board as Wise Clock 4, but a jumper connects SQW of DS3231 to D2 (INT2 interrupt pin of ATmega1284). This allows the software to take advantage of an ISR being called every second, which in turn updates the time on the display. Because this approach is in contrast to the existing Wise Clock 4 software (which did polling on RTC to update the time on the screen), its integration into the main trunk would be difficult, if not impossible.

In any case, the state machine "framework" can be replicated to most devices that can be described as a set of states with transitions between them. For example, for a device with 3 buttons like Wise Clock 4, the "framework" consists of 5 important functions:

1. the main loop() that checks the actions on the buttons;

void loop()
{
  int userAction = plusButton.checkButton();
  if (userAction != NO_ACTION)
  {
    // SET button was pressed;
    processPlusButton(userAction);
  }
  else
  {
    userAction = setButton.checkButton();
    if (userAction != NO_ACTION)
    {
      // SET button was pressed;
      processSetButton(userAction);
    }
    else
    {
      userAction = menuButton.checkButton();
      if (userAction != NO_ACTION)
      {
        // MENU button was pressed;
        processMenuButton(userAction);
      }
    }
  }
  executeState();
}

2. the function executeState(), which implements the functionality of each individual state;

void executeState()
{
  switch (crtState)
  {
    case STATE_SHOW_TIME:
      if (bUpdate)
      {
        bUpdate = false;
        getTimeFromRTC();
        displayDynamicTime(hours, minutes, seconds, &lastHour, &lastMin, &lastSec);
      }
      break;

    case STATE_COUNTDOWN:
      if (bUpdate)
      {
        bUpdate = false;
        if (isCountdownInProgress)
        {
          calculateTimeLeft();  // set cHours, sMinutes, cSeconds;
          displayDynamicCountdown(cHours, cMinutes, cSeconds, &lastCHour, &lastCMin, &lastCSec);

          if (cHours==0 && cMinutes==0 && cSeconds==0)
          {
            // countdown ends;
            isCountdownInProgress = false;
            showStaticTime(0, RED, 0, RED, 0, RED);
            beep();
          }
        }
      }
      break;

    case STATE_SET_TIME_HOUR:
      showStaticTime(hours, RED, minutes, crtColor, seconds, crtColor);
      break;

    case STATE_SET_TIME_MIN:
      showStaticTime(hours, crtColor, minutes, RED, seconds, crtColor);
      break;

    case STATE_SET_TIME_SEC:
      showStaticTime(hours, crtColor, minutes, crtColor, seconds, RED);
      break;

    case STATE_SET_CNTDWN_HOUR:
      showStaticTime(cHours, RED, cMinutes, ORANGE, cSeconds, ORANGE);
      break;

    case STATE_SET_CNTDWN_MIN:
      showStaticTime(cHours, ORANGE, cMinutes, RED, cSeconds, ORANGE);
      break;

    case STATE_SET_CNTDWN_SEC:
      showStaticTime(cHours, ORANGE, cMinutes, ORANGE, cSeconds, RED);
      break;
  }
}

3. one processing function per button (hence 3 functions: processPlusButton(), processMenuButton(), processSetButton()), each describing the transitions from every state based on user input;

void processPlusButton(int userAction)
{
  switch (crtState)
  {
    case STATE_SET_TIME_HOUR:
      if (userAction == BTN_PUSH)
      {
        hours++;
        if (hours>23) hours = 0;
      }
      break;

    case STATE_SET_TIME_MIN:
      if (userAction == BTN_PUSH)
      {
        minutes++;
        if (minutes>59) minutes = 0;
      }
      else if (userAction == BTN_DBL_PUSH)
      {
        minutes = minutes + 10;
        if (minutes>59) minutes = 0;
      }
      break;

    case STATE_SET_TIME_SEC:
      if (userAction == BTN_PUSH)
      {
        seconds++;
        if (seconds>59) seconds = 0;
      }
      else if (userAction == BTN_DBL_PUSH)
      {
        seconds = seconds + 10;
        if (seconds>59) seconds = 0;
      }
      break;

    case STATE_SET_CNTDWN_HOUR:
      if (userAction == BTN_PUSH)
      {
        cHours++;
        if (cHours>23) cHours = 0;
      }
      break;

    case STATE_SET_CNTDWN_MIN:
      if (userAction == BTN_PUSH)
      {
        cMinutes++;
        if (cMinutes>59) cMinutes = 0;
      }
      else if (userAction == BTN_DBL_PUSH)
      {
        cMinutes = cMinutes + 10;
        if (cMinutes>59) cMinutes = 0;
      }
      break;

    case STATE_SET_CNTDWN_SEC:
      if (userAction == BTN_PUSH)
      {
        cSeconds++;
        if (cSeconds>59) cSeconds = 0;
      }
      else if (userAction == BTN_DBL_PUSH)
      {
        cSeconds = cSeconds + 10;
        if (cSeconds>59) cSeconds = 0;
      }
      break;

    case STATE_SHOW_TIME:
      if (userAction == BTN_PUSH)
      {
        // change brightness;
        incrementBrightness();
      }
  }
}

void processMenuButton(int userAction)
{
  ht1632_clear();
  switch (crtState)
  {
    case STATE_SHOW_TIME:
      if (userAction == BTN_PUSH)
      {
// push MENU button to start setting up the start time for countdown;
        crtState = STATE_SET_CNTDWN_HOUR;
      }
      else if (userAction == BTN_HOLD)
      {
        crtState = STATE_SET_TIME_HOUR;
      }
      break;

    case STATE_SET_TIME_HOUR:
    case STATE_SET_TIME_MIN:
    case STATE_SET_TIME_SEC:
      if (userAction == BTN_PUSH)
      {
//----------------------------------------------------------------
// pressed the MENU button while setting the time;
// exit setting the time and resume time showing;
//----------------------------------------------------------------
        // save the time;
        setRtcTime();
        // return to the main screen (showing the time);
        crtColor = GREEN;
crtState = STATE_SHOW_TIME;
        showStaticTime((is24Hmode? hours : hours%12), crtColor, minutes, crtColor, seconds, crtColor);
        // also update the lastSec (for smooth rolling);
        lastSec = seconds;
      }
      break;

    case STATE_SET_CNTDWN_HOUR:
    case STATE_SET_CNTDWN_MIN:
    case STATE_SET_CNTDWN_SEC:
      if (userAction == BTN_PUSH)
      {
//----------------------------------------------------------------
// pressed the MENU button while setting the countdown time;
// exit setting the countdown time and start the countdown now;
//----------------------------------------------------------------
        isCountdownInProgress = true;
// total number of seconds for countdown;
cntDownTime = (long)((cHours * 3600l) + (cMinutes * 60) + cSeconds);
        // start displaying the countdown;
        crtColor = ORANGE;
        showStaticTime(cHours, crtColor, cMinutes, crtColor, cSeconds, crtColor);
        crtState = STATE_COUNTDOWN;
      }
      break;

    case STATE_COUNTDOWN:
      // pressing MENU while counting down will revert to normal clock mode;
      isCountdownInProgress = false;
      cHours = cMinutes = cSeconds = 0;
      // return to the main screen (showing the time);
      crtColor = GREEN;
      crtState = STATE_SHOW_TIME;
      showStaticTime((is24Hmode? hours : hours%12), crtColor, minutes, crtColor, seconds, crtColor);
      break;
  }
}

void processSetButton(int userAction)
{
  switch (crtState)
  {
    case STATE_SET_TIME_HOUR:
      if (userAction == BTN_PUSH)
      {
        crtState = STATE_SET_TIME_MIN;
      }
      break;

    case STATE_SET_TIME_MIN:
      if (userAction == BTN_PUSH)
      {
        crtState = STATE_SET_TIME_SEC;
      }
      break;

    case STATE_SET_TIME_SEC:
      if (userAction == BTN_PUSH)
      {
        crtState = STATE_SET_TIME_HOUR;
      }
      break;

    case STATE_SET_CNTDWN_HOUR:
      if (userAction == BTN_PUSH)
      {
        crtState = STATE_SET_CNTDWN_MIN;
      }
      break;

    case STATE_SET_CNTDWN_MIN:
      if (userAction == BTN_PUSH)
      {
        crtState = STATE_SET_CNTDWN_SEC;
      }
      break;

    case STATE_SET_CNTDWN_SEC:
      if (userAction == BTN_PUSH)
      {
        crtState = STATE_SET_CNTDWN_HOUR;
      }
      break;

    case STATE_SHOW_TIME:
      if (userAction == BTN_DBL_PUSH)
      {
        // change from 24H to 12H mode and viceversa;
        is24Hmode = !is24Hmode;
      }
      else if (userAction == BTN_PUSH)
      {
        // change font while showing time;
        crtFont++;
        if (crtFont >= NUMBER_OF_FONTS) crtFont=0;
        ht1632_clear();
      }
      else if (userAction == BTN_HOLD)
      {
        // change display mode between rolling and replace;
        displayMode++;
        if (displayMode > 1) displayMode=0;
      }
      // force screen refresh;
      showStaticTime((is24Hmode? hours : hours%12), crtColor, minutes, crtColor, seconds, crtColor);
      break;
  }
}

I did not use timeouts in the current state machine. I thought that timeouts would complicate the user experience, considering that one of the most important functions is the input of countdown time. Timing out from that input was not an option, I think. Therefore, the user is not forced to set up the time in a certain interval.

(*) NOTE: I developed the current software using Arduino 22 (because I started from the old version and I was too lazy to copy it over). To port to Arduino 1.0 and above, the "include WProgram.h" needs to be changed. But you already know the drill :)

Friday, June 20, 2014

Two-faced ("Kandinsky") Wise Clock

I managed to get the "Kandinsky" two-faced Wise Clock working with most of the relevant apps. The physical build looks and feels solid.




The software changes cover both single Kandinsky (2 opposite displays) and the double Kandinsky (4 displays, 2 on each side).

This new feature helped with eliminating a few bugs related to X_MAX (mostly wrong or hard-coded values). There are still a few remaining, but they only come to light on 4-display Wise Clocks (list from Nick):

'BIG' - only displays on the left two displays;
'TIX' - only displays on the left two displays;
'Words' - looks odd;
'Stopw' - only displays on the left two displays but time is centered;
'Score' - again, not centered; plus the button functions are swapped;
'Pacmn' - doesn't work at all;
'Lived' - again, not centered;
'Anim' - again, not centered;
'UTC' - again, not centered;
'Life' - fails totally, appears to revert to previous APP.

So, lots of work still....

I also resurrected the Wise Clock chronometer for a customer. The updated software will feature new fonts (one shown in the photo below), countdown timer, two-face (of course :), setting the time from buttons etc. Note that this code is on a different trunk than the Wise Clock code. It uses the 1Hz SQW interrupt from DS3231 to update the time on the screen.