Sep 16 2009

Wise Clock sketch

/**
 * WiseClock - displays text retrieved from I2C EEPROM, time from RTC;
 *           - display is either an 8x8 RG LED matrix or an 16x8 single color LED matrix
 *              i)  to select 16x8 single colour display, enable the line [#define _16x8_] below;
 *              ii) to select 8x8 RG display, disable (comment out) the line [#define _16x8_];
 *           - display is driven by 2x595s (for columns) and direct outputs (for rows);
 *           - display ISR (shiftOut the video mem) is executed on timer, 7812 times per second;
 *
 * Reference:
 * http://tinkerlog.com (email: alex@tinkerlog.com)
 * http://www.arduino.cc/en/Tutorial/ShiftOut
 * http://www.uchobby.com/index.php/2007/11/24/arduino-interrupts/
 *
 */

#include 
#include 
#include   // internal eeprom
#include 
#include 

// define this when display is 16x8, mono color;
#define _16x8_   true

// used to enable/disable Serial.print, time-consuming operation;
// to minimize the code size, all references to Serial should be commented out;
#define _DEBUG_     true

/*
* DONE:
*    (Mar 29/09) set clock by remote control, option 'S'; enter time as 4 digits, HHMM;
*    (Mar 29/09) sleep mode directly from "Power TV" button on the remote control;
*    (May 17/09) cover the single color 16x8 LED matrix (using same circuitry as RG LED matrix);
*    (Jun 03/09) disabled unused menu options; re-arranged menu options order and definitions;
*                reset display before sleep mode;
*    (Jun 20/09) set unused pins as outputs to save power (see http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1243213127/15#15);
*/

//-----------------------------------------------------------------------------
/* Pins used in Wise Clock are as follows:
	analog  0..2          - FREE to use (servo, buzzer, mux/demux select, tengu sound signal etc);
	analog  3 (INPUT)     – menu button;
	analog  4, 5          – I2C (RTC, eeprom);
	digital 0, 1          – serial (tx/rx)
	digital 2 (INPUT)     – IR receiver/ISR0;
	digital 3-13 (OUTPUT) – display;
*/
//-----------------------------------------------------------------------------

//--------------------------------------------------------------------------------------
/* DS1307 Real Time Clock - uses the wire library (I2C);
 *
 * RTC is set up through a separate sketch (+windows app talking on serial port).
 * I2C bus address of DS1307 is B1101000 (already defined in RTC.h).
 *
 * SETUP:     _ _
 *        X1-|oU |-Vcc
 *        X2-|   |-SQW/OUT square wave 1Hz output signal;
 *  Vbat(3V)-|   |-SCL     to Arduino analog 5
 *       GND-|   |-SDA     to Arduino analog 4
 *            ---
 *
 * Pull-up resistors (10k) are required on SDA (pins 5), SCL (pin 6), and SQW (pin 7).
 * Pin 7 (SQW) of DS1307 is not used (not connected to arduino).
 *
 *
 * References:
 *    http://www.datasheetcatalog.org/datasheet/maxim/DS1307.pdf
*/
//--------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------
/* 24LC256 256K EEPROM - uses the Wire library (I2C);
 *
 * The EEPROM stores messages as text lines separated by CR/LF (13/10).
 * Its content is already loaded with text messages (separate sketch, receiving data on COM from windows app/script).
 *
 * Addresses are ints - 0000-7FFF (32767) Data is bytes (8 bits x 32767 = 256K)
 * Functions for R/W of single byte or a page of bytes. Max page is 28 bytes.
 *
 * SETUP:           _ _
 * Arduino GND- A0-|oU |-Vcc  to Arduino Vcc
 * Arduino GND- A1-|   |-WP   to GND for now. Set to Vcc for write protection.
 * Arduino GND- A2-|   |-SCL  to Arduino analog 5
 * Arduino GND-Vss-|   |-SDA  to Arduino analog 4
 *                  ---       (A2, A1, A0 to GND for 1010000 (0x50) address.)
 *                            If set to Vcc adds to address (1010,A2,A1,A0)
 *
*/
//--------------------------------------------------------------------------------------

// I2C Bus address of 24LC256 256K EEPROM;
// if more than one eeprom, they will have different addresses (h/w configured);
#define I2C_ID     0x50

// global address of the last byte read from eeprom;
// set to a random value in setup();
unsigned int crtReadAddress = 0;

// push button on analog pin 3; used for menu selection (superseedes IR remote control);
#define MENU_BUTTON_PIN   3

// IR receiver (pin wired through 220 ohm resistor) on digital 2; serviced through ISR0;
#define IR_PIN            2

// pins used for LED matrix rows (multiplexed);
#define SHIFT_CLOCK_PIN   4
#define STORE_CLOCK_PIN   5
#define SER_DATA_PIN      6

// pins assigned to LED matrix columns;
byte pinForRow[8] = {8, 9, 10, 11, 12, 13, 7, 3};

//-----------------------------------------------------------------------------

// display colours (aka "pages");
#define BLACK   0
#define RED     1
#define GREEN   2
#define ORANGE  3

// menu options;
#define OPTION_BOTH_Q_T      0	// B (both quote and time)
#define OPTION_QUOTE         1	// Q
#define OPTION_TIME          2	// T
#define OPTION_SLEEP         3	// X
#define OPTION_SETTIME       4	// S (set time)

// maximum number of options in the menu; MUST be incremented when a new option is added!;
// REM: update when enable the other menu options;
#define MAX_OPTION           4

// set default option according to preference;
#define DEFAULT_OPTION       OPTION_QUOTE

// current menu option, a value between 0 and MAX_OPTION;
volatile int menuOption = DEFAULT_OPTION;

// letters displayed for each menu option;
char menuLetter[] = {'B', 'Q', 'T', 'X', 'S'};

// buffer used for storing the time when setting it;
int timeDigit[4] = {0};
// incremented as digits representing the time (HHMM) are entered;
// time will be set in RTC when index reaches 4 (meaning all 4 digits have been entered);
int indexTimeDigit = 0;

// parameters for IR receiver;
//#define start_bit  2000     // start bit threshold (microseconds)
#define IR_BIN_1      1000    // binary 1 threshold  (microseconds)
#define IR_BIN_0       400    // binary 0 threshold  (microseconds)

// SONY codes for the remote control;
#define SONY_CH_PLUS         144
#define SONY_CH_MINUS        145
#define SONY_VOL_PLUS        146
#define SONY_VOL_MINUS       147
#define SONY_MUTE            148
#define SONY_POWER_TV        149
#define SONY_1               128
#define SONY_2               129
#define SONY_3               130
#define SONY_4               131
#define SONY_5               132
#define SONY_6               133
#define SONY_7               134
#define SONY_8               135
#define SONY_9               136
#define SONY_0               137
#define SONY_DISPLAY         186
#define SONY_ENTER           139
#define SONY_TV_VIDEO        165
#define SONY_TV              170
#define SONY_JUMP            187

// button debouncing adapted from http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1210559123/7;
#define BOUNCE_TIME_BUTTON  600   // bounce time in ms for the menu button;
#define BOUNCE_TIME_IR      500   // bounce time in ms for the IR receiver;

// last time a command (IR or button) was received; used for debouncing;
volatile unsigned long menuTime = 0;

// indicates that one of the menu options (0..MAX_OPTION) is currently displayed;
boolean isMenuOptionOnDisplay = false;

// indicates that display needs to be refreshed;
boolean mustRefreshDisplay = false;

// when alternating between displaying quotes and time, start with the time;
boolean isDisplayingTime  = true;
boolean isDisplayingQuote = false;

// used to calculate the scrolling speed; value from 0 to 7, read in OPTION_SPEED menu option;
byte level = 5;

// interrupt handler for the infrared receiver (digital pin 2);
// it ignores codes received faster then the bounce time;
// only some commands determine the menu to be displayed;
void irReceiverISR()
{
  int key = getIRKey();
  if (key > 0)
  {
     switch (key)
     {
       case SONY_POWER_TV:
          menuOption = OPTION_SLEEP;
          menuTime = 0;    // display no menu;
          break;

       // used for selecting next up menu option;
       case SONY_CH_PLUS:
          menuOption++;
          if (menuOption > MAX_OPTION) menuOption = 0;
          mustRefreshDisplay = true;
          break;

       // used for selecting next down menu option;
       case SONY_CH_MINUS:
          menuOption--;
          if (menuOption < 0) menuOption = MAX_OPTION;
          mustRefreshDisplay = true;
          break;

       // used for adjusting the scrolling speed;
       case SONY_VOL_PLUS:
          level++;
          if (level > 7) level=0;
          break;

       // used for adjusting the scrolling speed;
       case SONY_VOL_MINUS:
          level--;
          if (level < 0) level=7;
          break;

       // used for starting new game (Conway's, dice etc);
       case SONY_ENTER:
          menuTime = 0;  // display no menu;
          break;

       // used for setting up the time;
       case SONY_1:
       case SONY_2:
       case SONY_3:
       case SONY_4:
       case SONY_5:
       case SONY_6:
       case SONY_7:
       case SONY_8:
       case SONY_9:
       case SONY_0:
          if (menuOption == OPTION_SETTIME)
          {
            timeDigit[indexTimeDigit] = (key - SONY_1 + 1) % 10;

            // display the just-entered digit;
#ifdef _16x8_
            setScreenMem_16x8('0' + timeDigit[indexTimeDigit]);
#else
            setScreenMem(RED, '0' + timeDigit[indexTimeDigit]);
#endif
            indexTimeDigit++;

            if (_DEBUG_)
            {
              Serial.print("index time digit: ");
              Serial.println(indexTimeDigit);
            }
          }
          break;
     }
  }
}

// executed as a result of the menu button being pressed;
// determines the menu to be displayed;
void processMenuButton()
{
  // debouncing;
  if (abs(millis() - menuTime) < BOUNCE_TIME_BUTTON)
    return;

  menuTime = millis();

  menuOption++;
  if (menuOption > MAX_OPTION) menuOption = 0;
  mustRefreshDisplay = true;
}

byte soft_prescaler = 0;

byte activeRow = 0;

// REM: should be one or the other, depending on _16x8_;
// video memory for the 16x8 display;
unsigned int screenMem_16x8[8] = {0};

// video memory for the 8x8 RG display;
byte screenMem[16] = {0};

// buffer for time display;
char timeBuffer[]   = " 12:45    ";
byte timeBufferPtr  = 0;
byte timeBufferSize = strlen(timeBuffer);

//----------------------------------------------------------------------------
// The displayed messages are read from eprom (24LC256).
// The eprom is already loaded with message lines, separated by CR (13).
// In order to be displayed, each line is first loaded from eprom into buffer "msgLine".
// After the line is displayed completely (on the scrolling display), a new line is loaded (read) from eprom.

byte lastReadByte = 0;

// current line string is built by copying each character to the position of the pointer, then advancimng the pointer;
char msgLine[200] = {0};
char* msgLinePtr = &msgLine[0];

int msgLineSize;	// size of the current string; set after line is read from eprom;
int msgLineIndex  = 0;
//----------------------------------------------------------------------------

// (fragments of) these 2 characters are on the screen at any moment;
byte char1 = 0;
byte char2 = 0;
// used for the 16x8 display;
byte char3 = 0;
byte char4 = 0;

// current colour used for display;
byte page = GREEN;

void setup()
{
  // Calculation for timer 2
  // 16 MHz / 8 = 2 MHz (prescaler 8)
  // 2 MHz / 256 = 7812 Hz
  // soft_prescaler = 15 ==> 520.8 updates per second
  // 520.8 / 8 rows ==> 65.1 Hz for the complete display
  TCCR2A = 0;           // normal operation
  TCCR2B = (1<> 8;
  byte lowByte  = screenMem_16x8[activeRow] & 0xFF;
  shiftOutRow(highByte, lowByte);

  // switch to new row;
  digitalWrite(pinForRow[activeRow], HIGH);
}

void shiftOutRow(byte red, byte green)
{
  digitalWrite(STORE_CLOCK_PIN, LOW);
  shiftOut(SER_DATA_PIN, SHIFT_CLOCK_PIN, LSBFIRST, red);
  shiftOut(SER_DATA_PIN, SHIFT_CLOCK_PIN, LSBFIRST, green);
  // return the latch pin high to signal chip that it
  // no longer needs to listen for information
  digitalWrite(STORE_CLOCK_PIN, HIGH);
}

void resetDisplay()
{
#ifdef _16x8_
  for (byte i = 0; i < 8; i++)  screenMem_16x8[i] = 0x00;
  char1 = 0;
  char2 = 0;
  char3 = 0;
  char4 = 0;
#else
  for (byte i = 0; i < 16; i++)  screenMem[i] = 0x00;
  char1 = 0;
  char2 = 0;
#endif

  // reset the buffer pointers;
  msgLineIndex  = 0;
  timeBufferPtr = 0;
}

// added Feb14/09 to delete the display by moving a dot from
// the upper left corner to the lower right corner;
void resetDisplayByMovingDot()
{
  byte mask;

  // the dot has the color of the current page;
  // REM: will not work for orange (both pages would need to be masked dot by dot);
  byte idx = ((page & GREEN) == GREEN)? 8 : 0;

  for (byte y=0; y < 8; y++)
  {
    for (byte x = 0; x < 8; x++)
    {
      // show dot at the particular location;
      mask = 1<<(7-x);
      screenMem[y+idx] = screenMem[y+idx] | mask;

      delay(40);

      // delete dot from the particular location, leaving location empty;
      mask = 0x7F >> x;
      screenMem[y+idx] = screenMem[y+idx] & mask;
    }
  }

  char1 = 0;
  char2 = 0;

  // reset the buffer pointers;
  msgLineIndex  = 0;
  timeBufferPtr = 0;
}

// statically displays the given sprite;
void setScreenMem(byte color, byte sprite[8])
{
  page = color;  // set the current page (color);

  byte row;
  for (byte i = 0; i < 8; i++)
  {
    row = sprite[i];
    if ((color & RED)   == RED)    screenMem[i]   = row;
    if ((color & GREEN) == GREEN)  screenMem[i+8] = row;
  }
}

// statically displays the given sprite in the middle of the 16x8 screen;
void setScreenMem_16x8(byte sprite[8])
{
  for (byte i = 0; i < 8; i++)
  {
    screenMem_16x8[i] = ((unsigned int) sprite[i]) << 4;
  }
}

// statically displays the specified character;
void setScreenMem(byte color, char charToDisplay)
{
  page = color;  // set the current page (color);

  byte row;
  for (byte i = 0; i < 8; i++)
  {
    int addrEeprom = (charToDisplay-32) * 8 + i;
    row = EEPROM.read(addrEeprom);
    if ((color & RED)   == RED)    screenMem[i]   = row;
    if ((color & GREEN) == GREEN)  screenMem[i+8] = row;
  }
}

// statically displays the specified character in the middle of the 16x8 display;
void setScreenMem_16x8(char charToDisplay)
{
  for (byte i = 0; i < 8; i++)
  {
    int addrEeprom = (charToDisplay-32) * 8 + i;
    byte line = EEPROM.read(addrEeprom);
    screenMem_16x8[i] = ((unsigned int) line) << 4;
  }
}

// at any given time, the display contains 2 fragments of characters, as they are scrolled from right to left;
void setScreenMem(byte sprite1[8], byte sprite2[8])
{
  byte row, rowChar1, rowChar2;

  // required here because level adjustment should have been an atomic operation, that is,
  // [level++; level = level % 7] in IR ISR should have been executed together; they sometimes are not;
  if (level > 7) level=0;
  if (level < 0) level=7;

  // adjusts the scrolling speed;
  byte wait = ((8-level) << 5) - 1;

  // scroll 5 times to the left;
  for (byte x=1; x<=5; x++)
  {
    // for each row;
    for (char i = 0; i < 8; i++)
    {
      rowChar1 = sprite1[i];
      rowChar2 = sprite2[i];

      row = ((rowChar1 << x) + (rowChar2 >> (5-x)));

      if ((page & RED)   == RED)     screenMem[i]   = row;
      if ((page & GREEN) == GREEN)   screenMem[i+8] = row;
    }
    delay(wait);
  }
}

void setScreenMem_16x8(byte sprite1[8], byte sprite2[8], byte sprite3[8], byte sprite4[8])
{
  // REM: this piece of code should be made into a function;
  // required here because level adjustment should have been an atomic operation, that is,
  // [level++; level = level % 7] in IR ISR should have been executed together; they sometimes are not;
  if (level > 7) level=0;
  if (level < 0) level=7;

  // adjusts the scrolling speed;
  byte wait = ((8-level) << 5) - 1;

  unsigned long row[16] = {0};

  // for each row;
  for (byte i = 0; i < 8; i++)
  {
      byte c1 = sprite1[i] >> 1;
      byte c2 = sprite2[i] >> 1;
      byte c3 = sprite3[i] >> 1;
      byte c4 = sprite4[i] >> 1;
      row[i] = ((((((unsigned long) c1 << 5) + c2) << 5) + c3) << 5) + c4;
  }  

  // scroll 5 times to the left (5 being the width of a char, as defined);
  for (byte x = 1; x <= 5; x++)
  {
    // for each row;
    for (byte i = 0; i < 8; i++)
    {
      screenMem_16x8[i] = row[i] >> (5-x);
    }
    delay(wait);
  }
}

void loop()
{
  // check menu button (connects analog pin 3 to ground);
  int val = analogRead(MENU_BUTTON_PIN);
  if (val < 5)
  {
    // menu button was pressed;
    processMenuButton();
  }

  // display the menu option for 5 seconds after menu button was pressed;
  if ((menuTime > 0) && (millis() - menuTime < 5000))
  {
      isMenuOptionOnDisplay = true;

      if (mustRefreshDisplay)
      {
        resetDisplay();
        // display menu option;
        // used to be a digit; switched to letter (more intuitive + more menu options available);
#ifdef _16x8_
        setScreenMem_16x8(menuLetter[menuOption]);
#else
        setScreenMem(GREEN, menuLetter[menuOption]);
#endif
        mustRefreshDisplay = false;
      }
  }
  else
  {
    // clear screen after displaying menu option for 5 seconds;
    if (isMenuOptionOnDisplay)
    {
      resetDisplayByMovingDot();
      isMenuOptionOnDisplay = false;
    }

    bigOptionSwitch();
  }
}

void updateTimeBuffer()
{
  int rtc[7];
  RTC.get(rtc, true);

  int second = rtc[0];
  int minute = rtc[1];
  int hour   = rtc[2];
  int day    = rtc[4];
  int month  = rtc[5];
  int year   = rtc[6];

  if (_DEBUG_)
  {
    Serial.print("Time is ");
    Serial.print(hour);
    Serial.print(":");
    Serial.print(minute);
    Serial.println("");
  }

  // build the string containing formatted time;
  timeBuffer[1] = (hour < 10) ? ' ' : ('0' + hour/10);
  timeBuffer[2] = '0' + hour%10;
  timeBuffer[4] = '0' + minute/10;
  timeBuffer[5] = '0' + minute%10;
}

// ignores codes received faster then the bounce time;
// returns negative (-1, -2, -3) if infrared reception error or when "debouncing";
// returns IR command (positive int) when code is read successfully;
int getIRKey()
{
  // "debouncing"; ignore IR command if coming too fast;
  if (abs(millis() - menuTime) < BOUNCE_TIME_IR)
    return -1;

  // wait for a start bit;
  if (pulseIn(IR_PIN, LOW, 200) < 2200)
    return -2;

  int data[12];
  // least significant bit first;
  data[0]  = pulseIn(IR_PIN, LOW);	// bits are only on low pulses;
  data[1]  = pulseIn(IR_PIN, LOW);
  data[2]  = pulseIn(IR_PIN, LOW);
  data[3]  = pulseIn(IR_PIN, LOW);
  data[4]  = pulseIn(IR_PIN, LOW);
  data[5]  = pulseIn(IR_PIN, LOW);
  data[6]  = pulseIn(IR_PIN, LOW);
  data[7]  = pulseIn(IR_PIN, LOW);
  data[8]  = pulseIn(IR_PIN, LOW);
  data[9]  = pulseIn(IR_PIN, LOW);
  data[10] = pulseIn(IR_PIN, LOW);
  data[11] = pulseIn(IR_PIN, LOW);
  // most significant bit last;

  int result = 0;
  int exponent = 1;

  for (int i=0; i<11; i++)
  {
    if (data[i] > IR_BIN_1)
    {
	data[i] = 1;    // bit is 1;
        result += exponent;
    }
    else
    {
	if (data[i] > IR_BIN_0)
        {
	  data[i] = 0;  // bit is 0;
	}
        else
        {
	  return -3;	// error;
	}
     }
     exponent = exponent << 1;
  }

  menuTime = millis();

  return result;
}

void displayAndScroll(char crtChar)
{
  // replace undisplayable characters with blank;
  if (crtChar < 32 || crtChar > 126)  crtChar = ' ';

  // indexes in the character bitmap definition table;
  char1 = char2;
  char2 = crtChar - 32;

  byte sprite1[8];
  byte sprite2[8];
  for (int i=0; i<8; i++)
  {
    sprite1[i] = EEPROM.read(char1 * 8 +i);
    sprite2[i] = EEPROM.read(char2 * 8 +i);
  }

  setScreenMem(sprite1, sprite2);
}

void displayAndScroll_16x8(char crtChar)
{
  // replace undisplayable characters with blank;
  if (crtChar < 32 || crtChar > 126)  crtChar = ' ';

  // indexes in the character bitmap definition table;
  char1 = char2;
  char2 = char3;
  char3 = char4;
  char4 = crtChar - 32;

  byte sprite1[8];
  byte sprite2[8];
  byte sprite3[8];
  byte sprite4[8];
  for (int i=0; i<8; i++)
  {
    sprite1[i] = EEPROM.read(char1 * 8 +i);
    sprite2[i] = EEPROM.read(char2 * 8 +i);
    sprite3[i] = EEPROM.read(char3 * 8 +i);
    sprite4[i] = EEPROM.read(char4 * 8 +i);
  }

  setScreenMem_16x8(sprite1, sprite2, sprite3, sprite4);
}

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;
}

// read, from I2C EEPROM, a whole line (ending with CR) into the message buffer;
void fetchLineFromEprom()
{
  byte lastReadByte = readNextByte();

  // after reaching the end of eprom's content, start from the beginning;
  if (lastReadByte == 0xFF || lastReadByte == 0)
  {
    crtReadAddress = 0;
    lastReadByte   = readNextByte();
  }

  while (lastReadByte != 13 && lastReadByte != 0xFF && lastReadByte != 0)
  {
    *msgLinePtr++ = lastReadByte;
    lastReadByte  = readNextByte();
  }

  // insert a few blanks (pause between messages);
  *msgLinePtr++ = ' ';
  *msgLinePtr++ = ' ';
  *msgLinePtr++ = ' ';

  // mark the end of the string;
  *msgLinePtr++ = 0;

//  Serial.println(msgLine);
  msgLinePtr  = &msgLine[0];      // reset the string pointer;
  msgLineSize = strlen(msgLine);  // update the size of the current string;
}

//------------------------------------------------------------------------
// put the arduino to sleep to save power;
// function copied from http://www.arduino.cc/playground/Learning/arduinoSleepCode;
//------------------------------------------------------------------------
void sleepNow()
{
    /* Now is the time to set the sleep mode. In the Atmega8 datasheet
     * http://www.atmel.com/dyn/resources/prod_documents/doc2486.pdf on page 35
     * there is a list of sleep modes which explains which clocks and
     * wake up sources are available in which sleep modus.
     *
     * In the avr/sleep.h file, the call names of these sleep modus are to be found:
     *
     * The 5 different modes are:
     *     SLEEP_MODE_IDLE         -the least power savings
     *     SLEEP_MODE_ADC
     *     SLEEP_MODE_PWR_SAVE
     *     SLEEP_MODE_STANDBY
     *     SLEEP_MODE_PWR_DOWN     -the most power savings
     *
     * For now, we want as much power savings as possible, so we
     * choose the according
     * sleep modus: SLEEP_MODE_PWR_DOWN
     *
     */
    set_sleep_mode(SLEEP_MODE_PWR_DOWN);   // sleep mode is set here

    sleep_enable();          // enables the sleep bit in the mcucr register so sleep is possible; just a safety pin;

    detachInterrupt(0);      // disables the previous routine (IR receiver ISR);
    attachInterrupt(0, wakeUpNow, LOW); // use interrupt 0 (pin 2) and run function wakeUpNow when pin 2 gets LOW (IR command);

    if (_DEBUG_)  Serial.println("going to sleep...");

    sleep_mode();        // the device is actually put to sleep! THE PROGRAM CONTINUES FROM HERE AFTER WAKING UP.

    if (_DEBUG_)  Serial.println("I am back!");

    sleep_disable();         // first thing after waking from sleep: disable sleep...

    detachInterrupt(0);      // disables interrupt 0 on pin 2 so the wakeUpNow code will not be executed during normal running time;
    attachInterrupt(0, irReceiverISR, LOW);
}

void wakeUpNow()        // here the interrupt is handled after wakeup
{
  // execute code here after wake-up before returning to the loop() function
  // timers and code using timers (serial.print and more...) will not work here.
  // we don't really need to execute any special functions here, since we just want the thing to wake up;
}

// function called when the 'Q' menu item is selected;
void displayingQuote()
{
        page = RED;

#ifdef _16x8_
        displayAndScroll_16x8(msgLine[msgLineIndex]);
#else
        displayAndScroll(msgLine[msgLineIndex]);
#endif

        msgLineIndex++;
        if (msgLineIndex >= msgLineSize)
        {
          fetchLineFromEprom();
          msgLineIndex = 0;
        }
}

// function called when the 'T' menu item is selected;
void displayingTime()
{
        page = ORANGE;
        updateTimeBuffer();

#ifdef _16x8_
        displayAndScroll_16x8(timeBuffer[timeBufferPtr]);
#else
        displayAndScroll(timeBuffer[timeBufferPtr]);
#endif

        timeBufferPtr++;
        if (timeBufferPtr >= timeBufferSize)
        {
          timeBufferPtr = 0;
        }
}

// function called when the 'B' menu item is selected;
void displayingBothQuoteAndTime()
{
  if (isDisplayingTime)
  {
    displayingTime();

    // when time buffer pointer wraps up, switch display to quote;
    if (timeBufferPtr == 0)
    {
      isDisplayingTime  = false;
      isDisplayingQuote = true;
      delay(800);
    }
  }

  if (isDisplayingQuote)
  {
    displayingQuote();

    // when msg buffer index wraps up, switch display to time;
    if (msgLineIndex == 0)
    {
      isDisplayingQuote = false;
      isDisplayingTime  = true;
      delay(800);
    }
  }
}

void setTime()
{
  Serial.print("set time to ");
  Serial.print(timeDigit[0]);
  Serial.print(timeDigit[1]);
  Serial.print(":");
  Serial.print(timeDigit[2]);
  Serial.print(timeDigit[3]);
  Serial.println("");

  RTC.stop();
  RTC.set(DS1307_SEC, 0);
  RTC.set(DS1307_MIN, timeDigit[2] * 10 + timeDigit[3]);
  RTC.set(DS1307_HR,  timeDigit[0] * 10 + timeDigit[1]);
  // dummy values, since they are not displayed anyway;
  RTC.set(DS1307_DOW,  1);
  RTC.set(DS1307_DATE, 1);
  RTC.set(DS1307_MTH,  1);
  RTC.set(DS1307_YR,   9);
  RTC.start();
}

void bigOptionSwitch()
{
    switch (menuOption)
    {
      case OPTION_QUOTE:
        displayingQuote();
        break;

      case OPTION_TIME:
        displayingTime();
        break;

      case OPTION_BOTH_Q_T:
        // alternate display between quote and time;
        displayingBothQuoteAndTime();
        break;

      case OPTION_SLEEP:
        // used for power saving; it puts the processor in "sleep mode power down";
        resetDisplay();
        delay(100);     // this delay is needed;
        sleepNow();
        break;

      // used for setting up the RTC's time;
      case OPTION_SETTIME:
        // expects 4 digits (HHMM) from the remote;
        // once they are entered, the mode changes to OPTION_TIME;
        // numeric keys are handled in the ISR (indexTimeDigit gets incremented there);
        if (indexTimeDigit > 3)
        {
          setTime();                 // set the time in RTC;
          menuOption = OPTION_TIME;  // quit setting time;
          indexTimeDigit = 0;
        }
        break;

      default:
        // nothing for now;
        break;
    }
}

Sep 2 2009

Sketch for LED displays from Sure

/***********************************************************************
 * HT1624.pde - Arduino demo program for Holtek HT1632 LED driver chip,
 *            As implemented on the Sure Electronics DE-DP016 display board
 *            (16*24 dot matrix LED module.)
 * Nov, 2008 by Bill Westfield ("WestfW")
 *   Copyrighted and distributed under the terms of the Berkely license
 *   (copy freely, but include this notice of original author.)
 *
 * Adapted for 8x32 display by FlorinC.
 ***********************************************************************/

// comment out this line for the 8x32 display;
//#define _16x24_

#include 
#include "ht1632.h"
#include 
#include "font3.h"

#ifdef _16x24_
  #define X_MAX 23
  #define Y_MAX 15
#else
  #define X_MAX 31
  #define Y_MAX 7
#endif

//(fc) switched to a different set of pins than the original, to accomodate the SD shield;
#define HT1632_DATA     6    // Data pin (pin 7)
#define HT1632_WRCLK    7    // Write clock pin (pin 5)
#define HT1632_CS       8    // Chip Select (1, 2, 3, or 4)

#define plot(x,y,v)  ht1632_plot(x,y,v)
#define cls          ht1632_clear

#define DISPDELAY 0

char* msg = "     Hello world";
int crtPos = 0;

/***********************************************************************
 * ht1632_chipselect / ht1632_chipfree
 * Select or de-select a particular ht1632 chip.
 * De-selecting a chip ends the commands being sent to a chip.
 * CD pins are active-low; writing 0 to the pin selects the chip.
 ***********************************************************************/

void ht1632_chipselect(byte chipno)
{
  DEBUGPRINT("\nHT1632(%d) ", chipno);
  digitalWrite(chipno, 0);
}

void ht1632_chipfree(byte chipno)
{
  DEBUGPRINT(" [done %d]", chipno);
  digitalWrite(chipno, 1);
}

/*
 * we keep a copy of the display controller contents so that we can
 * know which bits are on without having to (slowly) read the device.
 * Note that we only use the low four bits of the shadow ram, since
 * we're shadowing 4-bit memory.  This makes things faster, and we
 * use the other half for a "snapshot" when we want to plot new data
 * based on older data...
 */
// (fc) covers the case for 32x8 as well (64 bytes, 4 bits)
byte ht1632_shadowram[96];  // our copy of the display's RAM

/*
 * ht1632_writebits
 * Write bits (up to 8) to h1632 on pins HT1632_DATA, HT1632_WRCLK
 * Chip is assumed to already be chip-selected
 * Bits are shifted out from MSB to LSB, with the first bit sent
 * being (bits & firstbit), shifted till firsbit is zero.
 */
void ht1632_writebits (byte bits, byte firstbit)
{
  DEBUGPRINT(" ");
  while (firstbit) {
    DEBUGPRINT((bits&firstbit ? "1" : "0"));
    digitalWrite(HT1632_WRCLK, LOW);
    if (bits & firstbit) {
      digitalWrite(HT1632_DATA, HIGH);
    }
    else {
      digitalWrite(HT1632_DATA, LOW);
    }
    digitalWrite(HT1632_WRCLK, HIGH);
    firstbit >>= 1;
  }
}

/*
 * ht1632_sendcmd
 * Send a command to the ht1632 chip.
 * A command consists of a 3-bit "CMD" ID, an 8bit command, and
 * one "don't care bit".
 *   Select 1 0 0 c7 c6 c5 c4 c3 c2 c1 c0 xx Free
 */
static void ht1632_sendcmd (byte command)
{
  ht1632_chipselect(HT1632_CS);  // Select chip
  ht1632_writebits(HT1632_ID_CMD, 1<<2);  // send 3 bits of id: COMMMAND
  ht1632_writebits(command, 1<<7);  // send the actual command
  ht1632_writebits(0, 1); 	/* one extra dont-care bit in commands. */
  ht1632_chipfree(HT1632_CS); //done
}

/*
 * ht1632_clear
 * clear the display, and the shadow memory, and the snapshot
 * memory.  This uses the "write multiple words" capability of
 * the chipset by writing all 96 words of memory without raising
 * the chipselect signal.
 */
void ht1632_clear()
{
  char i;

  ht1632_chipselect(HT1632_CS);  // Select chip
  ht1632_writebits(HT1632_ID_WR, 1<<2);  // send ID: WRITE to RAM
  ht1632_writebits(0, 1<<6); // Send address
  for (i = 0; i < 96/2; i++) // Clear entire display
    ht1632_writebits(0, 1<<7); // send 8 bits of data
  ht1632_chipfree(HT1632_CS); // done
  for (i=0; i < 96; i++)
    ht1632_shadowram[i] = 0;
}

/*
 * ht1632_senddata
 * send a nibble (4 bits) of data to a particular memory location of the
 * ht1632.  The command has 3 bit ID, 7 bits of address, and 4 bits of data.
 *    Select 1 0 1 A6 A5 A4 A3 A2 A1 A0 D0 D1 D2 D3 Free
 * Note that the address is sent MSB first, while the data is sent LSB first!
 * This means that somewhere a bit reversal will have to be done to get
 * zero-based addressing of words and dots within words.
 */
static void ht1632_senddata (byte address, byte data)
{
  ht1632_chipselect(HT1632_CS);  // Select chip
  ht1632_writebits(HT1632_ID_WR, 1<<2);  // send ID: WRITE to RAM
  ht1632_writebits(address, 1<<6); // Send address
  ht1632_writebits(data, 1<<3); // send 4 bits of data
  ht1632_chipfree(HT1632_CS); // done
}

void ht1632_setup()
{
  pinMode(HT1632_CS, OUTPUT);
  digitalWrite(HT1632_CS, HIGH); 	// unselect (active low)
  pinMode(HT1632_WRCLK, OUTPUT);
  pinMode(HT1632_DATA, OUTPUT);
  ht1632_sendcmd(HT1632_CMD_SYSDIS);  // Disable system

#ifdef _16x24_
  ht1632_sendcmd(HT1632_CMD_COMS11);  // 16*32, PMOS drivers
#else
// (fc)
  ht1632_sendcmd(HT1632_CMD_COMS10);  // 32x8, PMOS drivers
#endif

  ht1632_sendcmd(HT1632_CMD_MSTMD); 	// Master Mode
  ht1632_sendcmd(HT1632_CMD_SYSON); 	// System on
  ht1632_sendcmd(HT1632_CMD_LEDON); 	// LEDs on

  for (byte i=0; i<64; i++)
    ht1632_senddata(i, 0);  // clear the display!

  delay(100);  // ?
}

/*
 * Copy a character glyph from the myfont data structure to
 * display memory, with its upper left at the given coordinate
 * This is unoptimized and simply uses plot() to draw each dot.
 */
void ht1632_putchar(int x, int y, char c)
{
  // fonts defined for ascii 32 and beyond (index 0 in font array is ascii 32);
  byte charIndex;

  // replace undisplayable characters with blank;
  if (c < 32 || c > 126)
  {
    charIndex = 0;
  }
  else
  {
    charIndex = c - 32;
  }

  // move character definition, pixel by pixel, onto the display;
  // fonts are defined as one byte per row;
  for (byte row=0; row<8; row++)
  {
    byte rowDots = pgm_read_byte_near(&myfont[charIndex][row]);
    for (byte col=0; col<6; col++)
    {
      if (rowDots & (1<<(5-col)))
        plot(x+col, y+row, 1);
      else
        plot(x+col, y+row, 0);
    }
  }
}

/*
 * plot a point on the display, with the upper left hand corner
 * being (0,0), and the lower right hand corner being (23, 15).
 * Note that Y increases going "downward" in contrast with most
 * mathematical coordiate systems, but in common with many displays
 * No error checking; bad things may happen if arguments are out of
 * bounds!  (The ASSERTS compile to nothing by default
 */
void ht1632_plot (int x, int y, char val)
{
  if (x<0 || x>X_MAX || y<0 || y>Y_MAX)
     return;

  char addr, bitval;

  /*
   * The 4 bits in a single memory word go DOWN, with the LSB
   * (first transmitted) bit being on top.  However, writebits()
   * sends the MSB first, so we have to do a sort of bit-reversal
   * somewhere.  Here, this is done by shifting the single bit in
   * the opposite direction from what you might expect.
   */
  bitval = 8>>(y&3);  // compute which bit will need set

#ifdef _16x24_
  addr = (x<<2) + (y>>2);  // compute which memory word this is in
#else
// (fc)
  addr = (x<<1) + (y>>2);  // compute which memory word this is in
#endif

  if (val) {  // Modify the shadow memory
    ht1632_shadowram[addr] |= bitval;
  }
  else {
    ht1632_shadowram[addr] &= ~bitval;
  }
  // Now copy the new memory value to the display
  ht1632_senddata(addr, ht1632_shadowram[addr]);
}

/*
 * get_shadowram
 * return the value of a pixel from the shadow ram.
 */
byte get_shadowram(byte x, byte y)
{
  byte addr, bitval;

  bitval = 8>>(y&3);  // compute which bit will need set
  addr = (x<<2) + (y>>2);  // compute which memory word this is in
  return (0 != (ht1632_shadowram[addr] & bitval));
}

/*
 * snapshot_shadowram
 * Copy the shadow ram into the snapshot ram (the upper bits)
 * This gives us a separate copy so we can plot new data while
 * still having a copy of the old data.  snapshotram is NOT
 * updated by the plot functions (except "clear")
 */
void snapshot_shadowram()
{
  for (char i=0; i< sizeof ht1632_shadowram; i++) {
    ht1632_shadowram[i] = (ht1632_shadowram[i] & 0x0F) | ht1632_shadowram[i] << 4;  // Use the upper bits
  }
}

/*
 * get_snapshotram
 * get a pixel value from the snapshot ram (instead of
 * the actual displayed (shadow) memory
 */
byte get_snapshotram(byte x, byte y)
{
  byte addr, bitval;

  bitval = 128>>(y&3);  // user upper bits!

#ifdef _16x24_
  addr = (x<<2) + (y>>2);  // compute which memory word this is in
#else
// (fc)
  addr = (x<<1) + (y>>2);  // compute which memory word this is in
#endif

  if (ht1632_shadowram[addr] & bitval)
    return 1;
  return 0;
}

/*
* This works equally well for both 16x24 and 8x32 matrices.
*/
void displayScrollingLine()
{
  // shift the whole screen 6 times, one column at a time;
  for (int x=0; x < 6; x++)
  {
    ht1632_putchar(-x, 0, msg[crtPos]);
    ht1632_putchar(-x+6,  0, ((crtPos+1 < strlen(msg)) ? msg[crtPos+1] : ' '));
    ht1632_putchar(-x+12, 0, ((crtPos+2 < strlen(msg)) ? msg[crtPos+2] : ' '));
    ht1632_putchar(-x+18, 0, ((crtPos+3 < strlen(msg)) ? msg[crtPos+3] : ' '));
    ht1632_putchar(-x+24, 0, ((crtPos+4 < strlen(msg)) ? msg[crtPos+4] : ' '));
    ht1632_putchar(-x+30, 0, ((crtPos+5 < strlen(msg)) ? msg[crtPos+5] : ' '));
    ht1632_putchar(-x+36, 0, ((crtPos+6 < strlen(msg)) ? msg[crtPos+6] : ' '));
    delay(DISPDELAY);
  }

  crtPos++;
  if (crtPos >= strlen(msg))
  {
    crtPos = 0;
  }
}

/***********************************************************************
 * traditional Arduino sketch functions: setup and loop.
 ***********************************************************************/

void setup ()
{
  ht1632_setup();
  Serial.begin(9600);
  cls();
}

void loop ()
{
  // display line;
  displayScrollingLine();
}

May 24 2009

Wise Clock

What could be better suited for the first Arduino project than a digital clock? No mechanical parts (servos etc), no voltage spikes to deal with, just some LEDs, a few easy to find components, lots of documentation (schematics, sketches) in the playground and elsewhere.

dome


May 22 2009

Hello world!

I created this blog to showcase my Arduino projects and to discuss challenges and solutions.

I think the Arduino platform is particularly powerful in the word of microcontroller boards because of the easy programming tool offered by Arduino IDE and also because of the extendability through the stackable “shields”.

But Arduino’s main strength is conferred by its open-sourceness. Compared with other microcontroller solutions (see Tom Igoe’s comparison article), Arduino is affordable, if not cheap, and now ubiquituous.

So welcome to this blog. Check it out often, contribute, express your opinions, be active!