Thursday, January 5, 2012

Odometer-effect Counter with Arduino and GLO-216 Serial OLED

Using a custom character in an unusual way, you can create a fun rolling-odometer visual effect with a GLO-type serial OLED. We've posted a sample Arduino program that does the trick. Here's a quick video:


Program listing is after the jump. (It's been tweaked slightly since first publication; now uses PROGMEM to store the digit bitmaps and instructions in program memory, sparing precious Arduino RAM.)
/*
Arduinodometer!
(GLO-216 and Arduino Uno) This program demonstrates a fun visual effect
in which the final digit of a counter is animated to 'roll up,' like
the tenths-of-a-mile digit on a mechanical odometer. 
 
Connect the GLO- to the Arduino +5 and GND, with serial input from 
pin 3 (using NewSoftSerial library or Arduino v1.0+). If the Spol jumper
on your GLO- has been cut (for UART-direct input), change the parameter 
"inverted" to "noninverted" in the program line: 
 SoftwareSerial mySerial =  SoftwareSerial(rxPin, txPin, inverted); 
*/

#include <avr/pgmspace.h>    // needed for PROGMEM
#include <SoftwareSerial.h>  // use any pin for serial out

#define rxPin 255      // Not used, set to invalid pin #
#define txPin 3        // Plug GLO's serial input  into this pin.
#define inverted 1     // If GLO- Spol jumper is intact (COM-polarity)
#define noninverted 0  // If GLO Spol jumper is cut (UART polarity)
#define odoChar 0x8F   // Custom character 15, at ASCII 143

// Bitmaps for the digits "0" - "9". Using PROGMEM to store them 
// in Flash. This saves 80 bytes of Arduino RAM (at the expense of 
// some code overhead). 
const byte digitPatterns[] PROGMEM = {
  0x0E,0x11,0x13,0x15,0x19,0x11,0x0E,0x80,  // 0
  0x04,0x0C,0x04,0x04,0x04,0x04,0x1F,0x80,  // 1
  0x0E,0x11,0x11,0x02,0x04,0x08,0x1F,0x80,  // 2
  0x1F,0x02,0x04,0x02,0x01,0x11,0x0E,0x80,  // 3
  0x02,0x06,0x0A,0x12,0x1F,0x02,0x02,0x80,  // 4
  0x1F,0x10,0x1E,0x01,0x01,0x11,0x0E,0x80,  // 5
  0x06,0x08,0x10,0x1E,0x11,0x11,0x0E,0x80,  // 6
  0x1F,0x01,0x02,0x04,0x08,0x08,0x08,0x80,  // 7
  0x0E,0x11,0x11,0x0E,0x11,0x11,0x0E,0x80,  // 8
  0x0E,0x11,0x11,0x0F,0x01,0x02,0x0C,0x80   // 9
} 
;

// This program includes a GLO-216 instruction that expects 8 bytes of data.
// If the program is reset during this download, the GLO would consume 
// our setup instructions thinking they were the expected data. So we'll 
// feed it some harmless characters as insurance. After that, clear
// the screen (0x0C), set the tall 1x16 font (0x03,0x02) and specify
// text-style numbers (0x14).
const prog_char clsSetBigFont[ ] PROGMEM = {
  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,  //reset insurance
  0x0C,0x03,0x02,0x14,0x00 } 
;

// This instruction consists of ctrl-A, ctrl-R, and the
// text number "6". Translation: 'Move to screen position 0,
// then right-align the number that follows in a 6-character 
// field.' Starting at position 0 and moving backward with ctrl-R
// causes the text to align to the righthand end of the screen.
const prog_char setPosRightAlign[ ] PROGMEM = {
  0x01,0x12,0x36,0x00} 
;

// Instruction ESC d 7, sets up redefinition of 
// custom character 15 (ASCII 143). The eight bytes sent
// after this instruction define the character bitmap.
const prog_char defineCC[ ] PROGMEM = {
  0x1B,0x64,0x37,0x00} 
;

// Main-count variable: 0-65535
unsigned int odoCount = 1024 ;

// RAM buffer for strings used by serial output, which can't 
// directly access strings stored in program memory with PROGMEM.
char buffer[15] ;

// Define a new serial port using the pin definitions above.
// Using inverted serial; if Spol is cut, change to noninverted.
SoftwareSerial mySerial =  SoftwareSerial(rxPin, txPin, inverted);

// SETUP: 
// Initialize the serial output, send some bytes to complete a 
// pending instruction, clear the screen and set the font, then 
// print a label. 
void setup() {
  digitalWrite(txPin, LOW);   // Stop bit state for inverted serial
  pinMode(txPin, OUTPUT);
  // set the data rate for the SoftwareSerial port
  mySerial.begin(9600);
  // give the GLO ample time to start up
  delay(500); 
  strcpy_P(buffer, clsSetBigFont);
  mySerial.print(buffer) ;
  mySerial.print("Odometer:") ;
}

// LOOP: 
// Print the main count, invoke the function that updates the 
// rolling custom-character pattern, and update the main count
// when the final digit rolls over. 
void loop() {
  // right align at the end of the display
  strcpy_P(buffer, setPosRightAlign);
  mySerial.print(buffer);
  // print the main count
  mySerial.print(odoCount) ;
  // now print the rolling final digit
  mySerial.write(odoChar) ;
  // roll up the last digit, and if it has 
  // rolled over to 0, increment main count
  if (incOdoChar() == 0) { 
    odoCount++ ; 
  }
  delay(25) ;
}

// incOdoChar() 
// Moves a pointer (odoIndex) through the digitPattern[]
// array to redefine the odo-digit custom character.
// This causes the "0" bitmap to scroll up row-by-pixel-
// row to be replaced by the "1" bitmap, which scrolls up...
// Function returns the index value; when this rolls over 
// to zero, the odoCount value should increment, just like 
// a real mechanical odometer. 
byte incOdoChar() {
  static byte odoIndex = 0 ;
  // send the instruction to redefine custom character
  strcpy_P(buffer, defineCC);
  mySerial.print(buffer);
  // send the eight-byte bitmap of the new CC pattern
  for (int i=0; i < 8; i++) {
    //retrieve the next byte from the flash/program memory table
    //and write it to the serial output 
    mySerial.write(pgm_read_byte(&digitPatterns[(i+odoIndex)%80]));
  }
  // increment for next time-- %80 to restrict 
  // to 0-79 (valid index range of digitPatterns[])
  odoIndex = ++odoIndex%80 ;
  // return index to signal rollover (0)
  return odoIndex ;
}

2 comments:

  1. Thanks. Program shows how to communicate with the GLO-216 from an Arduino, how to define a custom character, and how to use a custom character to create an animated effect. And it shows off the bright, hi-contrast OLED in one of its four font sizes (unlike LCDs, it can make seamless characters from 2 lines by 16 to 1 line by 8 (1x16 shown).

    Other than that, nothing much :)

    ReplyDelete