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.






Sunday, August 2, 2015

Introducing the "6-character alphanumeric LED Arduino shield"

This shield offers a quick way to add alphanumeric display capabilities to your Arduino project. It is based on the 14-segment 6-character LED display driven by two multiplexed MAX7221 drivers, through a method described in this application note.

 US$ 26 - free shipping to North America

The kit includes:
  • PCB
  • 2 x MAX7221 + 2 x 24-pin sockets
  • 6-character 14-segment common cathode LED display
  • 2 x 33k resistor
  • 2 x 100nF capacitor
  • machined header (as socket for the display)
  • male header (for the shield)


The board has a small prototyping area for adding project-specific parts, for example buttons and buzzer if you want to build an alarm clock (basic clock code for this shield here). Assembled, showing the time (from wsduino):


or, displaying text:


The shield uses Arduino pins D3, D4 and D5.


Sunday, July 12, 2015

Experimenting with MAX6955

MAX6955 is an interesting LED driver chip. It is the primordial charlieplexing device, being the materialization of a technique invented by Charlie Allen of Maxim Integrated. Without understanding how charelieplexing works, it is actually counter-intuitive to wire multiple (up to 8) 16-segment displays to such a driver chip. Fortunately, Maxim has great documentation on how to do it.


My experimenting actually started with MAX6954. After many failed tries due to SPI issues (Maxim uses a special interpretation of the protocol, I read), I switched to MAX6955.

MAX6955 is the I2C sibling of MAX6954 (which uses SPI). They both have identical LED driving abilities, only the microcontroller interface part of the chips differ. Once, both chips were available in DIP-40 package. Now, MAX6955 only comes in SSOP-36 (MAX6954 is still available in DIP-40). Luckily, the pin configurations for the two chips are compatible, which allows for easy swap. For this reason, I designed a breakout board (shared at oshpark), so I can use the same setup I built for MAX6954.


Beside Maxim's, there isn't much documentation on how to control these chips. One of the reasons they are rarely used by hobbyists is probably their price (about $20 at digikey), although in the same range as the wildly popular MAX72xx LED driver (available at around $10 from digikey). In "reality", for some reason, MAX72xx could be had for $1 or less on ebay, unlike MAX6954/6955. Therefore, a hobby kit designed around MAX6955 would be "extremely" expensive compared with others using different LED driving chips (e.g. MAX72xx).

MAX6954/6955 was designed to drive common cathode LED displays, like MAX72xx. But unlike MAX72xx, it cannot be used for displays with common segments (which require multiplexing), like the ones below (and used here). MAX6954/6955 requires the 14/16-segment displays to be single digit/character, because not all segments will be wired together.


Below is the Arduino sketch I wrote that works very well with MAX6955. Although it uses a minimal set of commands, it is capable of displaying a character at a given position, light up the dot, and even scroll a long message.

A nice feature of the chip is that the fonts are predefined, this making it an "intelligent" display driver, like Avago's HDSP-2534. To display a character, only its ASCII code needs to be supplied.

#include "Wire.h"

#define ID_MAX6955 B1100000 
#define NUM_DISPLAYS 6

#define max6955_reg_decodeMode      0x01
#define max6955_reg_globalIntensity 0x02
#define max6955_reg_scanLimit       0x03
#define max6955_reg_configuration   0x04
#define max6955_reg_displayTest     0x07
#define max6955_reg_digitType       0x0C

int writeMAX6955(char command, char data)
{
    Wire.beginTransmission(ID_MAX6955);
    Wire.write(command);
    Wire.write(data);
    Wire.endTransmission();
}

void initMAX6955()
{
    Wire.begin();
    // ascii decoding for all digits;
    writeMAX6955(max6955_reg_decodeMode, 0xFF);
    // brightness: 0x00 =  1/16 (min on)  2.5 mA/seg;
    // 0x0F = 15/16 (max on) 37.5 mA/segment
    writeMAX6955(max6955_reg_globalIntensity, 0x07);
    // active displays: 0x07 -> all;
    writeMAX6955(max6955_reg_scanLimit, 0x07);
    // set normal operation;
    writeMAX6955(max6955_reg_configuration, 0x01);
    // segments/display: 0xFF=14-seg; 0=16 or 7-seg;
    writeMAX6955(max6955_reg_digitType, 0x00);
    // display test off;
    writeMAX6955(max6955_reg_displayTest, 0x00);
}

void setup()
{
  // set D8 and D9 to GND (for I2C address);
  pinMode(8, OUTPUT);
  pinMode(9, OUTPUT);
  digitalWrite(8, LOW);
  digitalWrite(9, LOW);

  initMAX6955();
  delay(100);
  clear();
//  writeDisplay("HI");
//  writeChar(0, 'A', true);
//  scrollDisplay("     HELLO WORLD", 300);
  writeTime(15, 24, 39);
}

void loop()
{
}

void writeDisplay(char* msg)
{
  for (byte i=0; i < NUM_DISPLAYS; i++)
  {
    if (i < strlen(msg))
      writeMAX6955(0x25-i, msg[i]);
    else
      writeMAX6955(0x25-i, ' ');
  }
}

void writeChar(byte pos, char letter, boolean dotLit)
{
  writeMAX6955(0x25-pos, (dotLit? 0x80 : 0) | letter);
}

void clear()
{
  for (byte i=0; i < NUM_DISPLAYS; i++)
    writeMAX6955(0x25-i, ' ');
}

void scrollDisplay(char* msg, int delayMs)
{
  for (int i=0; i<=strlen(msg); i++)
  {
    writeDisplay(msg+i);
    delay(delayMs);
  }
}

void writeTime(int hours, int minutes, int seconds)
{
  char h1 = (hours/10)? '0' + hours/10 : ' ';
  writeChar(0, h1, false);
  char h2 = '0' + (hours%10);
  writeChar(1, h2, true);
  char m1 = '0' + (minutes/10);
  writeChar(2, m1, false);
  char m2 = '0' + (minutes%10);
  writeChar(3, m2, true);
  char s1 = '0' + (seconds/10);
  writeChar(4, s1, false);
  char s2 = '0' + (seconds%10);
  writeChar(5, s2, false);
}

The I2C address of B1100000 was set by grounding AD0 and AD1 (see table 5 in the datasheet on how to change that address). In my setup, these 2 pins are connected to D8 and D9. Don't forget to pull-up SDA and SCL with 4k7-10k resistors.

The left-most digit is at position 0, accessible at address 0x25. "Digit 1" is the second from left, accessible at address 0x24, and so on. This is determined by the wiring. In my case, "CC0" (in the table 1 of this application note) represents the right-most display, and it is accessible at address 0x20.

Wednesday, July 8, 2015

LED driver chips

After looking at the many options for driving LED displays (5x7/8x8 matrix, 7/14/16/25 segment, common anode/cathode, single/bi-color/RGB), I put together this list of commonly used LED driver chips, to have a better picture of possible combinations, and use it as reference for future projects.


The bottom 5 rows are not actually LED drivers, just substitutes (require current limiting resistors).

Some of the driver chips (e.g. "8x8" in the "channels" column) provide internal multiplexing, being designed specifically for driving array of LEDs. The others, where "channels" is just one number, would require extra circuitry (e.g. transistors) and logic (micro controller code) for multiplexing.

The "CA" column indicates "common anode", "CC" stands for common cathode.

There seem to be more options for driving common anode LED displays, probably because sinking current (by the chips' LED outputs) allows for higher currents and also for using a separate power source (usually higher voltage) for the LEDs.


Tuesday, July 7, 2015

Wise Clock 4 with the new 3mm 3216 LED display from Sure

My inventory of "old" 3216 3mm displays from Sure Electronics is now depleted. As everybody already knows by now, Sure redesigned the display to be powered with 12V instead of 5V (plus a few other cosmetic changes).
The Wise Clock 4 board cannot be plugged into the new display's connectors anymore, since they are now different (10 pins instead of 16), and the assignment of signals to pins has changed as well.

Making a seamless Wise Clock 4 with the new display is still possible, with a little more soldering work. The board will be connected to the display using the ribbon cable provided. To do this, cut one connector off the ribbon cable, then solder 5 wires (4 signal + ground) from the cut end of the ribbon, in the place of the header, as shown in the photo below.


The 5V power line from the clock's board (red wire in the photo) will be soldered separately from the ribbon cable, bypassing the display's 5V regulator, as shown in the next photo.


The new display is "top heavy", that is, it has the connectors and the 5V regulator (with large capacitors and inductors) at the top. These may interfere with the clock's buttons and the SD card. To avoid touching them accidentally, it is better to position the display upside down, as shown in the next photo.


For this reason, the sketch needs a little tweaking: all pixel-related functions in HT1632.cpp file will have to change the coordinates, like this:

void ht1632_plot (coord_t x, int8_t y, byte color)
{
#ifdef WANT_UPSIDE_DOWN
  x = X_MAX-1 - x;
  y = Y_MAX-1 - y;
#endif
...
}

The macro WANT_UPSIDE_DOWN is defined in UserConf.h.

Also notice that the Wise Clock 4 board is now mechanically  fixed to the back panel (because it's not plugged in the display's connectors anymore).
Needless to say that the laser-cut plates for the two version of the displays are not compatible, since the displays' dimensions are different.


I will continue to provide a "complete kit" that includes the new display, the 2 plates, and the screws + standoffs. The back plate won't have the 2 holes (for the board) drilled though. This "complete kit" may not have the two 2x8 female headers (since they are not needed, and will even be detrimental). In case the headers are in the kit, DO NOT SOLDER THEM if you use the new display.
The processor in the "complete kit" will be loaded with the sketch for the new display, with the software modification mentioned above.