NerdKits - electronics education for a digital generation

You are not logged in. [log in]

NEW: Learning electronics? Ask your questions on the new Electronics Questions & Answers site hosted by CircuitLab.

Microcontroller Programming » atmega8 + 5x7 LED Matrix scanning issue

February 03, 2013
by adosch
adosch's Avatar

I've been making a binary display clock using an atmega8 + 74HC595 + 5x7 LED matrix with a 32.768Khz crystal.

On the LED matrix, this is what will be displayed in binary light-up:

  • Column 1 - Minutes (0-59)
  • Column 2 - Hours (24-hour)
  • Column 3 - Year (YY - 00-99, so it fits 8-bit int)
  • Column 4 - Day (01-31)
  • Column 5 - Month (1-12)

My sketch is just has the basics so far:

  • Testing with a pre-set date+time by pre-loading the array --- easier to verify proper results this way. Add buttons later to set date+time.

  • Set my prescale and overflow value to create a TIMER1 interrupt once per second. The only thing that happens here is incrementing the second counter.

  • Simple conditional logic to increment second, minute, hour, year, day, and month (with leap year adjustment). I'm storing/updating this in a 8-bit unsigned integer 5 position array.

  • For scanning and POV portion, I'm using SPI to write each row byte out of the array that corresponds to the column to turn on LEDs, and the for loop incremented value flips through and turns on/off each column.

  • I've used all pre-scale types (8, 64, 256 an no prescale) --- I'm really after setting the overflow so the TIMER1 ofv interrupt trips every second; that's how I count the seconds elapsed.

Here's the problems I have:

  • I see quite a bit of flicker, so my display gives migrane headaches right now (O_o)

  • I chose the 32.768Khz crystal out of pure convenience since it divides nicely and it makes sense to use an external crystal for some accuracy to lessen the drift of the clock.

What I've tried:

  • I did try to only update the column and rows only where a bit was set in my row array byte to lessen the amount of needless scanning I needed to do if the LED col/row didn't need to be scanned through. Still flickers.

  • I'm leaning towards not having enough cycles with the 32k crystal to scan through all 35 leds (5x7 display). I was thinking it takes ~1ms x 35 = 35Khz, so exceeding 32k cycles I have now that I have now. I also tried only writing out bits that are marked as '1' for the row and then break out of the row loop when I hit the binary number length maximum for that column (e.g. miniute decimal number max is 59, which is 111011, or no need to scan higher than 6 out of 7 of the row positions for that column)

Code (so far) is below. Any input or suggestions welcome. Happy to learn.

#define F_CPU 32768
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include <stdlib.h>

#define SHIFT_REGISTER DDRB
#define SHIFT_PORT     PORTB
#define DATA           (1<<PB3)           //MOSI (SI)
#define LATCH          (1<<PB2)          //SS   (RCK)
#define CLOCK          (1<<PB5)          //SCK  (SCK)

#define COLUMN_COUNT   5

volatile uint8_t sec_ctr = 0;

volatile uint8_t datetime[5];

void init_avr(void) {
   DDRD = 0xFF;

   PORTD = 0;

   datetime[0] = 0b00000101;  // Minute (0-60), c1
   datetime[1] = 0b00000010; // Hour   (0-24), c2
   datetime[2] = 0b00001101; // Year   (0-99), c3
   datetime[3] = 0b00000011; // Day    (1-31), c4
   datetime[4] = 0b00000010;  // Month  (1-12), c5

   SHIFT_REGISTER |= (DATA | LATCH | CLOCK);     //Set control pins as outputs
   SHIFT_PORT &= ~(DATA | LATCH | CLOCK);        //Set contr

   SPCR = (1<<SPE) | (1<<MSTR);  //Start SPI as Master
}

void TIMER_init() {

   TIMSK |= (1 << TOIE1); //Enable overflow interrupt

   sei();

   // -------Prescale 256
   //TCCR1B |= (1<<CS12); //Prescaler (F_CPU/256) (127) (p99)
   //TCNT1 = 65408;

   // -------Prescale 64
   //TCCR1B |= ((1 << CS10) | (1 << CS11)); //Prescaler (F_CPU/64) (255) (p99)
   //TCNT1 = 65024;

   // -------Prescale 8
   //TCCR1B |= (1<<CS11); //Prescaler (F_CPU/8) (4095) (p99)
   //TCNT1 = 61440; // 64 prescale overflow

   // -------No Prescale
   TCCR1B |= (1<<CS10); //No Prescaler (F_CPU) (32767) (p99)
   TCNT1 = 32768; 
}

uint8_t is_leap_year(uint8_t year) {
   if ( year % 4 == 0  ) {
      // Leap year
      return 0;
   } else {
      // Not a leap year
      return 1;
   }
}

uint8_t get_days_in_month(uint8_t month, uint8_t year) {
   uint8_t is_leap;

   if ( month == 1 ) {
      return 31;
   } else if ( month == 2 ) {
      is_leap = is_leap_year(year);

      if (is_leap == 4) {
         return 29;
      } else {
         return 28;
      }

   } else if ( month == 3 ) {
      return 31;
   } else if ( month == 4 ) {
      return 30;
   } else if ( month == 5 ) {
      return 31;
   } else if ( month == 6 ) {
      return 30;
   } else if ( month == 7 ) {
      return 31;
   } else if ( month == 8 ) {
      return 31;
   } else if ( month == 9 ) {
      return 30;
   } else if ( month == 10 ) {
      return 31;
   } else if ( month == 11 ) {
      return 30;
   } else if ( month == 12 ) {
      return 31;
   }

   return 0;
}

void update_datetime(void) {
   uint8_t days_in_month;

   datetime[0]++; // increment minutes

   // Minute check to increment hours
   if ( datetime[0] > 59 ) {
      datetime[0] = 0; // reset minutes to 0
      datetime[1]++; // increment hours
   }

   // Hours check to increment days
   if ( datetime[1] > 23 ) {
      datetime[1] = 0; // reset hours to 0
      datetime[3]++; // increment days
   }

   // Days check to increment month
   days_in_month = get_days_in_month(datetime[4], datetime[2]);

   if ( datetime[3] > days_in_month ) {
      datetime[3] = 1; // reset days to 1
      datetime[4]++; // increment month
   }

   // Month check to increment year
   if ( datetime[4] > 12 ) {
      datetime[4] = 1; // reset month to 1
      datetime[2]++; // increment year
   }

   // Year check to reset on century
   if ( datetime[2] > 99 ) {
      datetime[2] = 0; // reset on century overturn
   }
}

int main(void) {

  init_avr();

  TIMER_init();

  while(1) {

      if (sec_ctr > 59) {
         sec_ctr = 0;
         update_datetime();
      }

      int c=0;
      int r=0;
      int b=0;

      for(c=0;c<5;c++) {
         //Shift in column data
         PORTD = (1<<c);

         for(r=0;r<9;r++) {

            b = (1<<r); // Set iteration bit on

            b &= datetime[c]; // bitwise AND to just set iteration bit on against column byte

            //if ((bit_is_set(b,r))) {

               //Pull LATCH low (Important: this is necessary to start the SPI transfer!)
               SHIFT_PORT &= ~LATCH;

               SPDR = b;

               //Wait for SPI process to finish
               while(!(SPSR & (1<<SPIF)));

               //Toggle latch to copy data to the storage register
               SHIFT_PORT |= LATCH;
               SHIFT_PORT &= ~LATCH;

            //} else {
            //   break;
            //}
        }
     }
  }

ISR(TIMER1_OVF_vect) {
   sec_ctr++;

   TCNT1 = 32768;
   //TCNT1 = 61440;
   //TCNT1 = 65024;
}
February 04, 2013
by Rick_S
Rick_S's Avatar

The best method I could think of for an operation like this would be to clock the mcu from the internal R/C Oscillator and turn off the DIV by 8 Fuse giving you an 8MHz clock. Then set Timer1 to a "screen" refresh rate of 40 to 60Hz. Setup an interrupt on Timer1 to set a flag for display refresh. In the main function, if that flag is detected, call a function that redraws the display. This should eliminate visible flicker.

For your time base, setup Timer2 in Asynchronous mode. Create an interrupt to increment time based on that timer. This way, your crystal isn't controlling the speed of the mcu, it's just setting a time base for real time on Timer2.

Just my thoughts,

Rick

February 04, 2013
by JimFrederickson
JimFrederickson's Avatar

Just to make sure I have this straight...

1 - you have 1 single 5x7 LED Matrix.
2 - you are using the 5 columns on the 5x7 Matrix to show a binary version of your data.
3 - you are using hardware SPI Commnications?

Is that correct?

Why do you have the loop beginning at: for(r=0;r<9;r++) {

Maybe I am not understanding something...

Aside from figuring out more of what your are actually doing, my personal thoughts/suggestions are as follows...

I never did understand why someone would use the 32khz watch crystal when there is an internal 8mhz Oscillator. (So for using the internal 8mhz Oscillator I am with Rick... I do like using the crystal for the clock for timer2 as well, even though I have never done it that way it is more elegant.)

Yes, "32khz watch crystals" may be more accurate. (Well NOT "maybe", they "WILL BE"...)
Yes, "32khz watch crystals" are the appropriate multiple of the time base we are used to...
Yes, the internal 8mhz oscillator does "wobble a bit". (Not a huge problem for shorter durations...)

Given those 2 things....

Use the internal oscillator. Correct for any uneven division in your program....

If you need more accuracy then use a "32khz watch crystal oscillator" attaching it's output to pin on your Microcontroller and then count the pulses.

If you need more accuracy still then use a battery backed realtime time clock.

February 05, 2013
by adosch
adosch's Avatar

Rick_S,

Thanks for the suggestions. I like that idea. I was always under the impression if I wanted to use that 32Khz clock for timing, I had to set the proper fuse bits and have the overall core clock run as it. I didn't know you could do it asynchronously. I'm still learning!

I ended up finding my solution to the flicker (which was my initial concern). I ended up setting PORTD=0 (e.g. turning off all the pins controlling the 5 columns) at the start of the loop, sleep for a small amount of time (~15us) then after I sent the row byte via SPI to the 74HC595, I turn the corresponding column pin on. I do still have an ever so tiny amount of flicker left, but it's not noticeable unless you really stare at it --- your suggestion will fix that, though Rick.

The loop doing the rows was purely experimental; I tried to do different scanning methods to turn individual LEDs on and off because I had current draw issues when lighting LEDs farther up the column. That code is now removed and it looks like this now:

      for(c=0;c<5;c++) {

         PORTD=0;
         _delay_us(15);

         //Pull LATCH low (Important: this is necessary to start the SPI transfer!)
         SHIFT_PORT &= ~LATCH;

         SPDR = datetime[c];

         //Wait for SPI process to finish
         while(!(SPSR & (1<<SPIF)));
         _delay_us(5);

         SHIFT_PORT |= LATCH;
         SHIFT_PORT &= ~LATCH;
         _delay_us(5);

         PORTD = (1<<c);
       }

I ended up just using a handful of NPN transistors and alternate current supply straight from my power source vs. using the power + low current source straight from the 74HC595 pins. That definitely helped cure my dim LED dilemma after the fact.

Post a Reply

Please log in to post a reply.

Did you know that you can connect to certain car computers via the OBD-II port with a microcontroller? Learn more...