Saturday, January 23, 2016

Break

I was going to say that this was the typical case of work/life taking precedence over hobbies. Not a good excuse though, since one can always find bits and pieces of time, like coins between couch cushions :). The best explanation for the long break is probably laziness. But I didn't just loiter around. With my desk full of just-started or half-finished projects screaming for time and attention, how could I? Here are some of the things I re-visited and tried to complete.

1. The Axiris IV-3 clock
As designed by Axiris team, the enclosure for the IV-3 shield can only accommodate one Arduino. The only way to make a real (with RTC) clock is to replace the Arduino with a wdsuino. Then, there is the issue of buttons (or any other way to set the time without using a PC, through USB). I managed to add an extra board that holds two buttons, accessible through the holes on the bottom side of the enclosure.
Below are some pictures.






The sketch I used is included below (based on the sample code from Axiris).

#include "Wire.h"
#include "DS1307.h"

#define PIN_BTN_SETHOUR 13
#define PIN_BTN_SETMIN  12

// globals; their values are set in getTimeFromRTC();
int hour;
int minute;
int second = 0;
int year, month, day;

// receive commands from serial port in this buffer;
char cmdBuffer[30] = {0};
byte nCrtBufIndex = 0;

// read time from DS1307 at intervals; for 5000, that's about 6 times a second;
#define MAX_TIME_READING_COUNTER  5000
long timeReadingCounter = MAX_TIME_READING_COUNTER;

/*
       pin 2
        ---
 pin 7 |   | pin 3
       |   |
 pin 8  ---
       |   | pin 4
 pin 6 |   |
        --- . pin 9
       pin 5
*/

static  byte  digit_seg_data[12*7] PROGMEM =
{
/* pin    2     3     4     5     6     7     8  */
        HIGH, HIGH, HIGH, HIGH, HIGH, HIGH,  LOW,    // Digit 0
         LOW, HIGH, HIGH,  LOW,  LOW,  LOW,  LOW,    // Digit 1
        HIGH, HIGH,  LOW, HIGH, HIGH,  LOW, HIGH,    // Digit 2
        HIGH, HIGH, HIGH, HIGH,  LOW,  LOW, HIGH,    // Digit 3
         LOW, HIGH, HIGH,  LOW,  LOW, HIGH, HIGH,    // Digit 4
        HIGH,  LOW, HIGH, HIGH,  LOW, HIGH, HIGH,    // Digit 5
        HIGH,  LOW, HIGH, HIGH, HIGH, HIGH, HIGH,    // Digit 6
        HIGH, HIGH, HIGH,  LOW,  LOW,  LOW,  LOW,    // Digit 7
        HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH,    // Digit 8
        HIGH, HIGH, HIGH, HIGH,  LOW, HIGH, HIGH,    // Digit 9
         LOW,  LOW,  LOW,  LOW,  LOW,  LOW, HIGH,     // Hyphen
         LOW,  LOW,  LOW,  LOW,  LOW,  LOW, LOW      // empty/not lit at all
};

typedef  struct  _TUBE
{
  byte    digit;      // 0..9
  byte    dot;        // HIGH or LOW
}
TUBE;

// Display state of the tubes (read-write): {digit, dot}
static  TUBE  tube_list[4] =
{
  { 10,  LOW },
  { 10,  LOW },
  { 10,  LOW },
  { 10,  LOW }
};

// Variables accessed at interrupt level
static  byte    cur_tube = 3;        // 0..3

ISR(TIMER1_COMPA_vect)
{
  const  byte  *digit_seg_p;
  byte          digit;
  byte          pin;

  // Clear pins as fast as possible
  PORTC &= ~0b00001111;  // Clear pin A[0..3]
  PORTD &= ~0b11111100;  // Clear pin 2..7
  PORTB &= ~0b00000011;  // Clear pin 8..9

  __builtin_avr_delay_cycles(8*40);  // 40 us (at 16 MHz)

  // Select the next tube
  cur_tube++;
  cur_tube %= 4;

  digit = tube_list[cur_tube].digit;
  digit_seg_p = digit_seg_data + 7*digit;
  for (pin = 2; pin < 9; pin++, digit_seg_p++)         digitalWrite(pin,pgm_read_byte(digit_seg_p));
  digitalWrite(pin,tube_list[cur_tube].dot);

  // Enable the current tube
  digitalWrite(A0+cur_tube,HIGH);
}

void  setup ()
{
  Serial.begin(9600);
  pinMode(A0,OUTPUT);
  pinMode(A1,OUTPUT);
  pinMode(A2,OUTPUT);
  pinMode(A3,OUTPUT);
  pinMode(2,OUTPUT);
  pinMode(3,OUTPUT);
  pinMode(4,OUTPUT);
  pinMode(5,OUTPUT);
  pinMode(6,OUTPUT);
  pinMode(7,OUTPUT);
  pinMode(8,OUTPUT);
  pinMode(9,OUTPUT);
  pinMode(10,OUTPUT);

  // Turn on the LEDs
  digitalWrite(10,HIGH);

// (fc) filaments, for my special case; normally this is not needed;
  pinMode(11,OUTPUT);
// power the filaments;
  analogWrite(11, 200);

  // set-time buttons;
  pinMode(12, INPUT_PULLUP);
  pinMode(13, INPUT_PULLUP);

  cli();

  // We've observed on Arduino IDE 1.5.8 that TCCR1A is non-zero at this point. 
  // So let's play safe and write all relevant timer registers.
  TCCR1A = 0b00000000;

// (fc) half the number for 8MHz Arduino
//  OCR1A  = 250-1;       // 250 Hz (62500/250)
  OCR1A  = 125-1;       // 250 Hz (62500/250)

  TCNT1  = 0;
  TIMSK1 = 0b00000010;
  TIFR1  = 0b00000000;
  TCCR1B = 0b00001100;  // Enable timer

  sei();
}

void loop()
{
  checkButtons();
  
  timeReadingCounter++;
  if (timeReadingCounter > MAX_TIME_READING_COUNTER)
  {
    getTimeFromRTC();
    display_time();

    timeReadingCounter = 0;
  }
}

static void display_time()
{
  unsigned long   ms = millis() % 1000;
  boolean        dot = (ms < 500) ? HIGH : LOW;
  byte            u;

  cli();
  u = hour;
  tube_list[1].digit = (u % 10);
  tube_list[1].dot = dot;

  tube_list[0].digit = ((u/10) ? u/10 : 11);
  tube_list[0].dot = LOW;

  u = minute;

  tube_list[3].digit = (u % 10);
  tube_list[3].dot = LOW;

  tube_list[2].digit = (u / 10);
  tube_list[2].dot = LOW;

  sei();
}

void getTimeFromRTC()
{
  int rtc[7];
  RTC_DS1307.get(rtc, true);

  // check to avoid glitches;
  if (rtc[DS1307_MIN] < 60 && rtc[DS1307_HR] < 24 && rtc[DS1307_SEC] < 60)
  {
    second = rtc[DS1307_SEC];
    minute = rtc[DS1307_MIN];
    hour   = rtc[DS1307_HR];
  }

  // check to avoid glitches;
  if (rtc[DS1307_YR] <= 2050 && rtc[DS1307_MTH] <= 12  && rtc[DS1307_DATE] <= 31)
  {
    day    = rtc[DS1307_DATE];
    month  = rtc[DS1307_MTH];
    year   = rtc[DS1307_YR];
  }
}

void setTime(int hh, int mm, int ss)
{
  getTimeFromRTC();

  // NOTE: when setting, year is 2 digits; when reading, year is 4 digits;
  RTC_DS1307.stop();
  RTC_DS1307.set(DS1307_SEC,  ss);
  RTC_DS1307.set(DS1307_MIN,  mm);
  RTC_DS1307.set(DS1307_HR,   hh);
  RTC_DS1307.set(DS1307_DOW,  1);
  RTC_DS1307.set(DS1307_DATE, day);
  RTC_DS1307.set(DS1307_MTH,  month);
  RTC_DS1307.set(DS1307_YR, year > 2000? year-2000 : year);
  RTC_DS1307.start();
}

void setDate(int newyear, int newmonth, int newday)
{
  getTimeFromRTC();

  // NOTE: when setting, year is 2 digits; when reading, year is 4 digits;
  RTC_DS1307.stop();
  RTC_DS1307.set(DS1307_SEC,  second);
  RTC_DS1307.set(DS1307_MIN,  minute);
  RTC_DS1307.set(DS1307_HR,   hour);
  RTC_DS1307.set(DS1307_DATE, newday);
  RTC_DS1307.set(DS1307_MTH,  newmonth);
  RTC_DS1307.set(DS1307_YR,   newyear);
  RTC_DS1307.set(DS1307_DOW,  1);
  RTC_DS1307.start();
}

void checkButtons()
{
  // increment hours and minutes;
  if (LOW == digitalRead(PIN_BTN_SETHOUR))
  {
    hour++;
    if (hour>23) hour = 0;
    setTime(hour, minute, 0);
    delay(200);
  }
  if (LOW == digitalRead(PIN_BTN_SETMIN))
  {
    minute++;
    if (minute > 59) minute = 0;
    setTime(hour, minute, 0);
    delay(200);
  }
}

2. WiFiChron1284 edition, featuring ATmega1284 SMD
This should be the "ultimate" upgrade for the WiFiChron board. The ATmega1284 processor will provide extra room for the ESP8266 WiFi code (debugging, parsing RSS feeds, data buffering etc).
The PCB design of the new board is shown below. (The boards are ordered and being manufactured).



3. Adapter for dual HDSP display
This adapter, suggested by Ray S, is the new addition to the collection of adapters developed for HDSP clock or WiFiChron.
Combined with the above mentioned WiFiChron1284, it would make a miniature Wise Clock 4 (without the SD card though). The two HDSP-2534 displays share all signals except for the CE (chip enable). The second display uses pin 10 for CE. Writing to each display is done by enabling the right CE line. This allows independent control of each display (dimming, text scrolling etc.).

Photos of the prototype are shown below.