Yet Another POV Clock Question

Hello all you Arduino experts out there.

I am working on a POV clock project for a final project for my Associates Degree. So far I have learned how to do basic programming of my Arduino UNO, such as blinking LED's and a Knight Rider sequence. I also did a very very basic POV that will spell out a predefined word that reads from an X-Y grid. I have looked at many many many internet sites for Arduino specific POV clock code but I cannot find much. Although I have found plenty using PIC's. I am hoping someone can point me into some Arduino sketch code to make one.

So far here is what my plans are and what parts I have.

I would like to use 8-LED's but I am willing to change that if its too much of a problem (I don't think it would be much different than a smaller cluster of LED's)

I also want the characters to appear as 5 LED's wide, which seems pretty common.

I am going to be using an Adafruit RTC DS1307 breakout board to take care of the clock stuff, (setting the time from a computer) but I am not sure how to interface this into the POV code. ( I have seen a basic code for a clock that displays on the serial display and I understand how that works but I don't know how to use the RTC DS1307).

The motor speed control will be independent from the Arduino stuff.

My electronics experience is very extensive so I am just asking for help with a POV Clock code or help getting an RTC clock code to work with a POV code. I do have some experience in programming some other microcontrollers like 8051's in assembly (yuck), and I have learned how to write C code but not at an expert level.

Thanks everyone.

Oh and please go easy on me since this is my very first post and I am new to this stuff.

8 LEDs should be fine, as that will be 8 pixels high, right? That sounds fine.

I thought the Blinkenlight shield page had example code for that sort of thing. Check out this thread:

http://arduino.cc/forum/index.php?topic=77474.0

If he can display "Blinkenlight" you can display the time.

You basically need to find the time from your clock, turn that into a string (hint: sprintf). Then do a table lookup to find the bit pattern for the relevant digit and turn each column on in the sequence you desire.

It should be pretty simple, have a stab at it and see how you go.

Just to get you started since the bit management might not be obvious, I modified one of the Blinkenlight examples to show arbitrary text:

#include <Wire.h>
#include <MsTimer2.h>

// font data - each character is 8 pixels deep and 5 pixels wide

byte font [96] [5] PROGMEM = {
  { 0x00, 0x00, 0x00, 0x00, 0x00 }, // space  (0x20)
  { 0x00, 0x00, 0x2F, 0x00, 0x00 }, // !
  { 0x00, 0x07, 0x00, 0x07, 0x00 }, // "
  { 0x14, 0x7F, 0x14, 0x7F, 0x14 }, // #
  { 0x24, 0x2A, 0x7F, 0x2A, 0x12 }, // $
  { 0x23, 0x13, 0x08, 0x64, 0x62 }, // %
  { 0x36, 0x49, 0x55, 0x22, 0x50 }, // &
  { 0x00, 0x05, 0x03, 0x00, 0x00 }, // '
  { 0x00, 0x1C, 0x22, 0x41, 0x00 }, // (
  { 0x00, 0x41, 0x22, 0x1C, 0x00 }, // (
  { 0x14, 0x08, 0x3E, 0x08, 0x14 }, // *
  { 0x08, 0x08, 0x3E, 0x08, 0x08 }, // +
  { 0x00, 0x50, 0x30, 0x00, 0x00 }, // ,
  { 0x08, 0x08, 0x08, 0x08, 0x08 }, // -
  { 0x00, 0x30, 0x30, 0x00, 0x00 }, // .
  { 0x20, 0x10, 0x08, 0x04, 0x02 }, // /
   
  { 0x3E, 0x51, 0x49, 0x45, 0x3E }, // 0  (0x30)
  { 0x00, 0x42, 0x7F, 0x40, 0x00 }, // 1
  { 0x42, 0x61, 0x51, 0x49, 0x46 }, // 2
  { 0x21, 0x41, 0x45, 0x4B, 0x31 }, // 3
  { 0x18, 0x14, 0x12, 0x7F, 0x10 }, // 4
  { 0x27, 0x45, 0x45, 0x45, 0x39 }, // 5
  { 0x3C, 0x4A, 0x49, 0x49, 0x30 }, // 6
  { 0x01, 0x71, 0x09, 0x05, 0x03 }, // 7
  { 0x36, 0x49, 0x49, 0x49, 0x36 }, // 8
  { 0x06, 0x49, 0x49, 0x29, 0x1E }, // 9
  { 0x00, 0x36, 0x36, 0x00, 0x00 }, // :
  { 0x00, 0x56, 0x36, 0x00, 0x00 }, // ;
  { 0x08, 0x14, 0x22, 0x41, 0x00 }, // <
  { 0x14, 0x14, 0x14, 0x14, 0x14 }, // =
  { 0x00, 0x41, 0x22, 0x14, 0x08 }, // >
  { 0x02, 0x01, 0x51, 0x09, 0x06 }, // ?
  
  { 0x32, 0x49, 0x79, 0x41, 0x3E }, // @  (0x40)
  { 0x7E, 0x11, 0x11, 0x11, 0x7E }, // A
  { 0x7F, 0x49, 0x49, 0x49, 0x36 }, // B
  { 0x3E, 0x41, 0x41, 0x41, 0x22 }, // C
  { 0x7F, 0x41, 0x41, 0x22, 0x1C }, // D
  { 0x7F, 0x49, 0x49, 0x49, 0x41 }, // E
  { 0x7F, 0x09, 0x09, 0x09, 0x01 }, // F
  { 0x3E, 0x41, 0x49, 0x49, 0x7A }, // G
  { 0x7F, 0x08, 0x08, 0x08, 0x7F }, // H
  { 0x00, 0x41, 0x7F, 0x41, 0x00 }, // I
  { 0x20, 0x40, 0x41, 0x3F, 0x01 }, // J
  { 0x7F, 0x08, 0x14, 0x22, 0x41 }, // K
  { 0x7F, 0x40, 0x40, 0x40, 0x40 }, // L
  { 0x7F, 0x02, 0x0C, 0x02, 0x7F }, // M
  { 0x7F, 0x04, 0x08, 0x10, 0x7F }, // N
  { 0x3E, 0x41, 0x41, 0x41, 0x3E }, // O
  
  { 0x3F, 0x09, 0x09, 0x09, 0x06 }, // P  (0x50)
  { 0x3E, 0x41, 0x51, 0x21, 0x5E }, // Q
  { 0x7F, 0x09, 0x19, 0x29, 0x46 }, // R
  { 0x46, 0x49, 0x49, 0x49, 0x31 }, // S
  { 0x01, 0x01, 0x7F, 0x01, 0x01 }, // T
  { 0x3F, 0x40, 0x40, 0x40, 0x3F }, // U
  { 0x1F, 0x20, 0x40, 0x20, 0x1F }, // V
  { 0x3F, 0x40, 0x30, 0x40, 0x3F }, // W
  { 0x63, 0x14, 0x08, 0x14, 0x63 }, // X
  { 0x07, 0x08, 0x70, 0x08, 0x07 }, // Y
  { 0x61, 0x51, 0x49, 0x45, 0x43 }, // Z
  { 0x00, 0x7F, 0x41, 0x41, 0x00 }, // [
  { 0x02, 0x04, 0x08, 0x10, 0x20 }, // backslash
  { 0x00, 0x41, 0x41, 0x7F, 0x00 }, // ]
  { 0x04, 0x02, 0x01, 0x02, 0x04 }, // ^
  { 0x40, 0x40, 0x40, 0x40, 0x40 }, // _
  
  { 0x00, 0x01, 0x02, 0x04, 0x00 }, // `  (0x60)
  { 0x20, 0x54, 0x54, 0x54, 0x78 }, // a
  { 0x7F, 0x50, 0x48, 0x48, 0x30 }, // b
  { 0x38, 0x44, 0x44, 0x44, 0x20 }, // c
  { 0x38, 0x44, 0x44, 0x48, 0x7F }, // d
  { 0x38, 0x54, 0x54, 0x54, 0x18 }, // e
  { 0x08, 0x7E, 0x09, 0x01, 0x02 }, // f
  { 0x0C, 0x52, 0x52, 0x52, 0x3E }, // g
  { 0x7F, 0x08, 0x04, 0x04, 0x78 }, // h
  { 0x00, 0x44, 0x7D, 0x40, 0x00 }, // i
  { 0x20, 0x40, 0x44, 0x3D, 0x00 }, // j
  { 0x7F, 0x10, 0x28, 0x44, 0x00 }, // k
  { 0x00, 0x41, 0x7F, 0x40, 0x00 }, // l
  { 0x7C, 0x04, 0x18, 0x04, 0x78 }, // m
  { 0x7C, 0x08, 0x04, 0x04, 0x78 }, // n
  { 0x38, 0x44, 0x44, 0x44, 0x38 }, // o
  
  { 0x7C, 0x14, 0x14, 0x14, 0x08 }, // p  (0x70)
  { 0x08, 0x14, 0x14, 0x08, 0x7C }, // q
  { 0x7C, 0x08, 0x04, 0x04, 0x08 }, // r
  { 0x48, 0x54, 0x54, 0x54, 0x20 }, // s
  { 0x04, 0x3F, 0x44, 0x40, 0x20 }, // t
  { 0x3C, 0x40, 0x40, 0x20, 0x7C }, // u
  { 0x1C, 0x20, 0x40, 0x20, 0x1C }, // v
  { 0x3C, 0x40, 0x30, 0x40, 0x3C }, // w
  { 0x44, 0x28, 0x10, 0x28, 0x44 }, // x
  { 0x0C, 0x50, 0x50, 0x50, 0x3C }, // y
  { 0x44, 0x64, 0x54, 0x4C, 0x44 }, // z
  { 0x00, 0x08, 0x36, 0x41, 0x00 }, // {
  { 0x00, 0x00, 0x7F, 0x00, 0x00 }, // |
  { 0x00, 0x41, 0x36, 0x08, 0x00 }, // }
  { 0x30, 0x08, 0x10, 0x20, 0x18 }, // ~
  { 0x7F, 0x55, 0x49, 0x55, 0x7F }  // unknown char (0x7F)
  
};

char displayThis [40] = { 0 };
byte pos, pixel;

void blink() 
  {
  // get current letter
  char thisLetter = displayThis [pos];
  
  // look up bit pattern for this pixel (table starts at 0x20 - space)
  byte bitPattern = pgm_read_byte ( &font [thisLetter - 0x20] [pixel++]);
 
  // output to LEDs
  PORTD = bitPattern;
  
  // move onto next letter if required
  if (pixel >= 5)
    {
    pos++;
    if (displayThis [pos] == 0)  // back to start
      pos = 0;  
    }  // if done all 5 pixels

}  // end of blink

void setup()
  {
  DDRD = 0b11111111; // set digital  0- 7 to output
  MsTimer2::set(2, blink);
  MsTimer2::start();
} // end of setup

void loop()
  {
  sprintf (displayThis, "hello world");
  delay (1000);
  }  // end of loop

I pulled in a font I used for a graphical LCD display. It’s a bit of an overkill for just digits, but hey, you can display text as well.

In loop you need to set up what is going to be displayed. Then the timer kicks in on an interrupt and outputs the bits into D0 to D7 rapidly.

I found it a bit fiddly to test because just waving the thing around the bits were backwards half the time. If you have it on a motor it should be OK.

I made a much more complex system and used POV as a means of display. I started a blog post but didn't finish. Towards the end you will see a clock on POV display:

http://liudr.wordpress.com/2011/04/03/persistence-of-vision-2/

Since this is for your degree, you should work out the details yourself. I'll give you a hierarchy I used for display on pov

update_column->print_char->print_msg

If you can do print column of light, then find a way to print character with Nick's array of data, then you're done on the printing part.

I kept the array in SRAM on an ATMEGA168 so only 1K SRAM and it was enough :)

Thanks for the info guys.

Nick I didn’t know that the sprintf() command would work in Arduino sketches. Liudr, I did see your site in web searches and I really liked your setup. Do you guys happen to know of an example code that is for a POV clock? I just need to see how it is structured that way I can work on mine.

Also, I forgot to ask in my original post about using an optic sensor. Is it necessary? I only ask because it seems that most POV display examples that I have seen are using it to control the motor speed, but since I am not using the microprocessor to control the motor speed is it needed.

Thanks again
Phillip B.

Take a look. some details removed. I was using the rotating stage to measure centripetal acceleration in a physics lab so you will see the accelerometer stuff.

#include <stdio.h>
#include "characters.h" // This file includes all the characters in bit map
#include "buttons.h"
#include "stat.h"
#define myPWR 2  // This powers the accelerometer when using 2009
#define myGND 3  // This powers the accelerometer when using 2009
#define myClockPin 10  // The clock pin for the shift registers
#define myLatchPin 11  // The latch pin for the shift registers
#define myDataPin 12  // The data pin for the shift registers
#define mySensorPin 9 // This is the sensor for POV
#define mySensorPin2 8 // This is the sensor2 for POV
#define loopPeriod 100
#define ax_pin 3 // analog pin to sense x acceleration
#define ay_pin 0 // analog pin to sense y acceleration
//#define ay_pin 2 // analog pin to sense y acceleration
#define az_pin 2 // analog pin to sense z acceleration
#define ax_base 506 // Base line for x acceleration when the accelerometer is laying flat (ay=0);
#define ay_base 512 // Base line for y acceleration when the accelerometer is laying flat (ay=0);
#define az_base 512 // Base line for z acceleration when the accelerometer is laying flat (ay=0);
#define gx_factor 19.46 // How many digital levels per g for x. Since it's ratiometric, 512 levels for full range of 18g so 28 levels per g.
#define gy_factor 19.46 // How many digital levels per g for y. Since it's ratiometric, 512 levels for full range of 18g so 28 levels per g.
#define gz_factor 19.46 // How many digital levels per g for z. Since it's ratiometric, 512 levels for full range of 18g so 28 levels per g.

// font doubles in width at 288 ms period
buttons btn_1(mySensorPin, LOW);
buttons btn_2(mySensorPin2, LOW);
stat ax_stats; // This creates a stat object to track acceleration ax. When constructors have no parameters, this convention requires the () removed.
stat ay_stats; // This creates a stat object to track acceleration ay. When constructors have no parameters, this convention requires the () removed.
stat az_stats; // This creates a stat object to track acceleration az. When constructors have no parameters, this convention requires the () removed.

unsigned char ledStatusBits;  // This variable contains the LED status bits for up to 32 LEDs.
unsigned long last_trigger;
unsigned long this_trigger;
unsigned long period;
int abs_level; // Direct reading from analog
int x_level, y_level; // acceleration A/D level with base taken away;
int last_number=0;
float voltage;
float a_g;
float w=0;// Angular velocity rev/s
void setup()                    // run once, when the sketch starts
{
  pinMode(myDataPin,OUTPUT);
  pinMode(myClockPin,OUTPUT);
  pinMode(myLatchPin,OUTPUT);
}

void loop()                     // run over and over again
{
  char msg[24]="";
  last_trigger=millis();
  updateLed(0); // Clear the LED array
  while(1)
  {
    if ((btn_1.sense()==buttons_down)||(btn_2.sense()==buttons_down)) 
    {
      this_trigger=millis();
      period=this_trigger-last_trigger;
      last_trigger=this_trigger;
      if (period>1000) period=100; // Makes sure it is not stuck at a very low speed.
      w=1000/(float)(period)/2;// Since two sensors are being triggered per period with two display head, the angular velocity is half the inverse of period.
      abs_level=analogRead(ay_pin);
      y_level=abs_level-ay_base;
      a_g=((float)y_level)/gy_factor;

      sprintf(msg," %1d.%02d RPS %2d.%02dg", int(w), int((w-int(w))*100),  int(a_g), int((a_g-int(a_g))*100));
      displayMsg(msg,int(2000/w),1);
    }
  }
}

void updateLed(unsigned char first8)  // Updates LED status using shift registers.
{
  digitalWrite(myLatchPin, LOW);  // Disable update to the output buffers.
  shiftOut(myDataPin, myClockPin, LSBFIRST, first8);//MSBFIRST when flat LSBFIRST when standing.
  digitalWrite(myLatchPin, HIGH);  // Enable update to the output buffers.
}

void displayMsg(char msg[], unsigned int delayus, unsigned char spacing) // delayus is the delay between scan line/column/characters.
{
//calls displayChar
}

void displayChar(unsigned char ch, unsigned int delayus)// delayus is the delay between scan line/columns.
{
//calls updateLed
}