Showing posts with label I2C. Show all posts
Showing posts with label I2C. Show all posts

Friday, May 10, 2019

Clock super-display

Today was a good day. In typical fashion, I started a few new "projects" almost in the same time. First one, it's assembling of a new kind of clock, from a kit sent by Nick S. I got stuck pretty early though, so I "parked" it for now. Details to come soon, in a special post.

Second one, an "Adler 121PD" vintage calculator with a VFD display, that I found "in the dumpster" (well, not really, but the idea is the same, I got it for free). I was going to break it apart, for the display and the circuitry, but I gave up when I powered it up (with an improvised cable; the original, proprietary one, was missing) and it actually worked! I may still go ahead with dis-assembling it, since it is not a great value anyway; I checked prices on ebay, and they go for around $20.

Lastly, the project that gave the name of this post: a clock LED super-display, consisting of 3 individual and independent indicators, inspired by the Leitch studio clock, brought to my attention by Nick (VE2HOT). The goal for the clock super-display is to eventually be able to emulate the Leitch clock. Here it is, in its incipient glory (only the back panel; the black wooden frame not pictured):


Since I am not the crafty kind-of-guy (also not keen on spending for form more than for content), I am always looking for cheap, easy and quick solutions for encasing electronics. In this case, Ikea's Ribba 9"x9" frame ($10) seems to be a good fit for the job, and hopefully will help the future clock look "Leitchy" or even better (Nick's photo below):


The 2 alphanumeric displays (4 and 8 chars) of the clock super-display are I2C-driven. The 60 LED ring is adafruit neopixel, controlled by a single pin. With this setup, even an ESP8266 module could be used as the brains of the clock.

The ring is fixed to the cardboard back/panel of the deep Ikea frame with four M3 plastic standoffs glued to the PCB.
The 4-character alphanumeric 16-segment is my creation, introduced earlier. It is driven by the HT16K33 backpack, also from adafruit (not in the picture). The PCB has M3 holes for screws.
The 8-character alphanumeric is made of two side-by-side quad 14-segment LED displays, also from adafruit. The 2 modules already have the HT16K33 drivers installed (soldered on the back). Attaching these quad displays to the panel is not easy, since the holes are probably M1.4. Even these thin M1.4 screws need to be forced, because the screw head presses against display's plastic enclosure. Eventually, the M1.4 screws will be glued to the M3 plastic standoffs, that's the best I could come up with. It is weird that, for such a popular and successful product, one cannot find photos (or instructions) on mounting these modules using screws.

Next step is the software support in the WiFiChron software. Also need to find a way to access the 3 buttons: having them in the back is not a good idea, having them in the front is impossible, unless the glass is replaced with transparent/smoky/grey acrylic, which can be drilled.

Saturday, April 13, 2019

WiFiChron support for 16-segment LED display

This is the second time I am writing this post. First time it just disappeared after almost 2 hours of editing. I started the post by saying that whenever I want to have some electronics fun, I open one of my drawers. Nice story line, but I am too frustrated now to recreate it from memory. (The lesson I learned is that I should write it first as a document, save locally, then copy and paste into a blog post.)
So I will keep it short and dry.

Some time ago, I designed this "4-character 16-segment 1-inch LED" board (pictured below), briefly mentioned here. I abandoned it, after a couple of failed tries, while writing the character definitions. Since then, I discovered the Adafruit 4-char alphanumeric LED backpack, which comes with nice software support as well.


For WiFiChron, two cascaded modules make an 8-character display functionally similar to HDSP-2534, but bigger and more visible. With the "Display Abstraction Layer" already in place, software support should be easy to integrate, since controlling it with the HT16K33 breakout allows the re-use of the above mentioned Adafruit LED backpack library. For maximum compatibility, I followed the same wiring, then connected the two extra segments, A2 and D2, to pin 10 (not connected for the 14-segment backpack) and pin 11 (connected to the DP), respectively.


I added a new class, Alphanum8x16, to the original files (Adafruit_LEDBackpack.h and cpp) to control the extra segments:

class Alphanum8x16 : public Adafruit_AlphaNum4 {  public:   void writeDigitAscii(uint8_t n, uint8_t ascii); };
void Alphanum8x16::writeDigitAscii(uint8_t n, uint8_t a) {   uint16_t font = pgm_read_word(alphafonttable+a);   displaybuffer[n] = font;   //--------------------------------------------------------   // this is the Adafruit mapping of digits to segments:   // 0 DP N M L K J H G2 G1 F E D C B A   //   // this is the 16 seg mapping of digits to segments:   // A2 D2 N M L K J H G2 G1 F E D1 C B A1   //   // bits:   // 1  1  1 ...                 ...  1 0   // 5  4  3   //   // Note: DP is not connected/controlled for the 16 seg;   //--------------------------------------------------------   // if A1 (bit 0) is on, set A2 (bit 15) as well;   if (font & 1)     displaybuffer[n] |= 0x8000;   // if D1 (bit 3) is on, set D2 (bit 14) as well;   if (font & 8)     displaybuffer[n] |= 0x4000; }
The 8x16-segment display is implemented in class DisplayHT16K33 in the WiFiChron software.
So far, WiFiChron can support the following displays (defines in DAL.h):

//#define DISPLAY_HDSP2534
//#define DISPLAY_DL1414
#define DISPLAY_HT16K33
//#define DISPLAY_OLED
//#define DISPLAY_HT1632
//#define DISPLAY_MAX6955

In principle, any display that can show 8 characters can be used through DAL.


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.

Thursday, October 23, 2014

OLED clock with Pacman mode

This large 2.42" OLED I2C module can be used as a replacement for the smaller 0.96" display for which I originally designed the ProMini OLED clock shield. It is built around the same SSD1306 chip, and it only comes in yellow (for now).

MikeM wrote this great Pacman clock sketch for it (available for download here), shown in the video below.



To display on the OLED, Mike used U8glib graphic library. Initially tested on the 0.96" OLED, the sketch froze after some time, for a yet to be explained reason. The same sketch works perfectly with the 2.42" display. Mike spent a lot of time trying to figure out if the culprit is software (bug in the U8glib) or hardware.
(Any feedback on this issue is greatly appreciated.)

The clock can also display the time as HH:MM:SS on the whole screen, as shown in the photo below, also courtesy of Mike.


My next step would be to design an enclosure for it. For that I will probably need some more help :)

Update Nov 29, 2014, from MikeM:
A lot of credit goes to MikeR, I took the Tetris and Pacman faces from his sketch and adapted them for the OLED shield.  The Pong code is modified from the WiseClock sketch.  The digital, analog, and QR code faces are mine, although I did use a QR code library I found.  I did credit MikeR inside of the code.

Saturday, August 30, 2014

ProMini clock shield with OLED display

Yet another ProMini clock shield kit, this time featuring a 128x64 I2C OLED display.


The kit can be purchased with or without the OLED display (I prefer you buy the OLED on your own, for example this excellent one from miker).

  US$30, includes OLED display, free shipping to North America

  US$16, OLED not included, free shipping to North America

The kit includes:
  • PCB
  • DS1307 SMD
  • 32kHz crystal
  • CR1220 coin battery
  • battery holder
  • optional: I2C 128x64 OLED display (blue or white)
  • tactile switch (2x)
  • resistor 10k (2x)
  • machined male pins


The PCB was designed to accommodate I2C OLED displays with the 4-pin header configured either as VCC-GND-SDA-SCL or as VCC-GND-SCL-SDA.

The OLED clock can also be powered from the same LiPo battery shield for ProMini, as used in the bubble clock. To minimize current consumption (beside disabling the ProMini on-board LEDs), the processor can be awaken from sleep at the push of the "hours" button (on D3).

Schematic and board layout are shown below.



The OLED clock could show the time in many different ways, including Pong mode (sketch adapted from miker), analog clock mode, digital clock mode (sample sketches to be provided soon).

ProMini clock shield with 7-segment bubble display

Update May 6, 2016: This kit is no longer offered until I get a new batch of QDSP-6064 displays (whose price seem to have jumped considerably).

This clock was designed as a ProMini shield. It comes as a mostly-SMD kit, based on DS1307 with battery backup and the QDSP-6064 7-segment LED "bubble" display.


The kit includes the following:
  • PCB
  • QDSP-6064
  • DS1307 SMD
  • 32kHz crystal
  • CR1220 coin battery
  • battery holder
  • 330 ohm resistor 0805 (8x)
  • tactile switch SMD (2x)
  • machined female pins



The assembled clock can be fitted with a LiPo battery shield for ProMini, as shown in this post (source code also provided there).
The current draw (measured at 20mA with an unmodified ProMini) can be minimized by removing the 2 LEDs on the ProMini board, as well as dimming the 7-segment bubble display through software (SevSeg library). One other way of maximizing the LiPo battery life cycle is by waking the clock from sleep mode at the press of the "minutes" button (on D2).

Schematic and board layout are shown below.




Friday, January 20, 2012

I2SDv3 - Arduino buckler with microSD

The Wyolum machine (these are the people who generously offered $3000 in innovation grants, with no strings attached) is forging ahead with a new and improved version of I2SD.
I just received their v3 prototype and it looks impressive. I must say it is the most feature-rich data logger / SD card backpack (here is the list of the competing products that I compared with).

Like its predecessor, I2SD v3 is a software-compatible Arduino (ATmega328/16MHz) with extras. It has on-board microSD card, DS3231 extremely accurate real-time-clock with backup battery, infrared receiver and 2 LED indicators for errors or status.





























I2SDv3 comes assembled (all SMD), with the bootloader burnt in. Sketches can be uploaded through the FTDI connector.

The board can be plugged directly into Arduino, using one row of headers (A0-A4-GND-RST), hence the name "buckler" (like a "semi-shield", got it?)
I2SDv3 also offers header access to D4-D7 (v2 lacked that; my complaint was heard :), and it is compatible with the ChronoDot headers.

To test it, I decided to try the OpenLog library, by Nathan Seidle of Sparkfun. Surprisingly, it worked without a glitch from the first try. Well, kind of, I had to read the documentation :), and to change HardwareSerial.cpp, a "system file" (function SIGNAL(USART_RX_vect) is redefined in OpenLog.pde).

To emulate the OpenLog board closer, I changed the code to use D2 for the status LED, as shown:

from
#define STAT1  5 //On PORTD
int statled1 = 5;  //This is the normal status LED

to
#define STAT1  2 //On PORTD
int statled1 = 2;  // status LED on I2SDv3;

Note: The second LED of OpenLog board is connected to SCK (D13), so it blinks when the SD card is active (while reading or writing). The second LED on I2SDv3, being on D3, cannot be easily re-purposed.

Following OpenLog documentation, I connected to I2SDv3 using CoolTerm, typed in some text, pressed CtrlZ three times and voila!: file LOG0001.TXT got created and it contained the characters I typed in. Cool indeed.

Note: OpenLog won't compile with Arduino 1.0 IDE without some minor changes, as follows:
1. "WProgram.h" replaced everywhere with "Arduino.h"
2. function SdFile::write(uint8_t) must return size_t now (since it is virtual function defined in Stream.h); both SdFile.cpp and SdFat.h will need to be updated to reflect that.

Reminder: The OpenLog library should work with FAT32-formatted SD cards as well as FAT16. I will test it as soon as I get a 4GB microSD card.

Saturday, October 1, 2011

I2SD kit

A while ago I received the good-looking I2SD kit from Wyolum. This is another open source project created and generously shared by the same team that brought us ClockTHREE and C3Jr.

The I2SD kit is pictured below. As you can see, it is mostly SMD (0805 and SOIC packages). Not for a novice, but definitely easy to solder by anyone with a steady hand and a good pair of eyes.















As always, when I assemble a kit, I try to skip the "read the manual" part. I don't recommend this to anyone though. This is just a test for me to assess how user-friendly the kit is. (Note: Ironically, this test would fail for my own Wise Clock 3 kit because of the 3 resistors of 4K7. One really need to know exactly where those resistors go (R5, R6 and R7). An improvement in a future version of Wise Clock 3 would be to have the resistor values on the silkscreen, as C3Jr has.)
Well, the I2SD kit passed the "no manual required" test, which means that the kit is well thought and designed. Again, if you are not sure what you are doing, you should not attempt building this kit without reading the documentation first.

The next photos show the top and bottom sides of the assembled I2SD, with the SD card inserted into the socket.




























As you can notice from the photos, I2SD has an on-board ATmega328, thus making it an (software-wise) Arduino-compatible. It also has an SD socket, an RTC chip (DS3231) with backup battery, and the 6-pin FTDI connector.

The I2SD was designed to be used as an Arduino storage peripheral, linked with Arduino devices on I2C.
For this purpose, the I2SD runs a sketch that:
- receives "read" commands from a host and returns the required data it reads from the SD card file;
- receives "write" commands from the host and stores the data in the SD card file.

As mentioned above, the I2SD itself can be used as a standalone Arduino (since the ATmega328 has the bootloader). To test it, I uploaded (with the Arduino IDE 22) this sketch which writes to the file TEST.TXT on the SD card.

I2SD can be integrated within many devices requiring data storage and retrieval, ranging from data logging (the RTC chip is definitely helping here), to system configuration, image capturing etc.

Saturday, June 27, 2009

Storing strings in EEPROM, byte by careful byte

By popular demand (meaning somebody asked :), in this article I will show how I store ASCII characters(1) in an I2C EEPROM, and then how I fetch them for display purpose.

(1) In this article, “character” and “byte” have the same meaning, since one ASCII character can be stored in a byte.

I used this solution in Wise Clock, where 32KB (aprox 16 book pages) of text(2) is displayed line by line, as a sequence of quotations.
(2) "Text" is a sequence of ASCII characters.
Obviously, before the text is retrieved from EEPROM, the text must be written, once, to EEPROM. So, the very first step is to load the text into the EEPROM. To do this, I use two pieces of software:
  • one sketch (running on Arduino) that reads, from the serial port, byte by byte, and then writes it into the EEPROM;

void loop()
{
if (isWrite)
{
if (Serial.available() > 0)
{
// read next character from COM port;
byte incomingByte = Serial.read();
if (isCommand && (incomingByte == 'r' || incomingByte == 'l'))
{
isWrite = false;
Serial.println("read eprom");
switch (incomingByte)
{
case 'r':
isReadLineByLine = false;
break;
case 'l':
isReadLineByLine = true;
break;
}
}
else
{
isCommand = false;
if (incomingByte == '\\')
{
// don't write the backslash;
isCommand = true;
}
else
{
// the following debug lines (serial.print...) take about 25 more ms than the write itself (10 ms);
// it is a good idea to comment them out if we transfer the content (32K) of a whole file;
/*
Serial.print("write to addr ");
Serial.print(crtWriteAddress);
Serial.print(" byte ");
Serial.println(incomingByte, BYTE);
*/
writeNextByte(incomingByte);
}
}
}
}

  • one application (running on PC and already presented here) that reads the characters from a text file and sends them to serial port, byte by byte.
Once in the EEPROM, the text can be retrieved when required. In Wise Clock, I chose to read the text from the EEPROM one line at a time. Since each line ends with a CR (“carriage return”), ASCII character 13, to get a line of text I read characters until a CR is found


else // reading the eeprom;
{
if (isReadLineByLine)
{
// read a whole line into the message buffer;
lastReadByte = readNextByte();
while (lastReadByte != 13 && lastReadByte != 0xFF)
{
*msgBufferPtr++ = lastReadByte;
lastReadByte = readNextByte();
}
*msgBufferPtr++ = 0;
Serial.println(msgBuffer);
msgBufferPtr = &msgBuffer[0]; // reset buffer pointer;
delay(50);
}
else
{
if (0xFF != lastReadByte)
{
Serial.print("read from addr ");
Serial.print(crtReadAddress);
Serial.print(" byte ");
lastReadByte = readNextByte();
Serial.print(lastReadByte, BYTE);
Serial.print(" (");
Serial.print(lastReadByte, DEC);
Serial.println(")");
delay(50);
}
}
}
}
If you want to cut an paste this code, you may need the following lines as well:
// I2C Bus address of 24LC256 EEPROM;
// if more than one eeprom, they will have different addresses (h/w configured);
#define I2C_ID 0x50
// global address of the last written byte;
unsigned int crtWriteAddress = 0;
// global address of the last read byte;
unsigned int crtReadAddress = 0;
boolean isWrite = true;
byte lastReadByte = 0;
// current line string is built by copying each character to the position of the pointer, then advancing the pointer;
char msgBuffer[200] = {0};
char* msgBufferPtr = &msgBuffer[0];
boolean isCommand = false;
boolean isReadLineByLine = false;
void writeByte(int i2cId, unsigned int eeaddress, byte data)
{
int rdata = data;
Wire.beginTransmission(i2cId);
Wire.send((int)(eeaddress >> 8)); // Address High Byte
Wire.send((int)(eeaddress & 0xFF)); // Address Low Byte
Wire.send(rdata);
Wire.endTransmission();
delay(10); // NEED THIS DELAY!
}
void writeNextByte(byte data)
{
writeByte(I2C_ID, crtWriteAddress, data);
crtWriteAddress++;
}
byte readByte(int i2cId, unsigned int eeaddress)
{
byte rdata = 0xFF;
Wire.beginTransmission(i2cId);
Wire.send((int)(eeaddress >> 8)); // Address High Byte
Wire.send((int)(eeaddress & 0xFF)); // Address Low Byte
Wire.endTransmission();
Wire.requestFrom(i2cId, 1);
if (Wire.available()) rdata = Wire.receive();
return rdata;
}
byte readNextByte()
{
byte rdata = readByte(I2C_ID, crtReadAddress);
crtReadAddress++;
return rdata;
}
(The last 4 I2C functions are copied and adapted from playground.)