Timer

Right, so i now have timer running (although i dont think at the right speed cos the seconds are not seconds),

and i am trying to get it so when a push switch is press twice, the lcd displays the time between presses. Naturally i cant quite get it to do exacly that. Can anyone see where i;m going wrong?

        // F_CPU tells util/delay.h our clock frequency
      //  #define F_CPU 1000000UL   // Orangutan frequency (1MHz)

        #include <avr/io.h>
        #include <util/delay.h>

      

        //-----------------------------------------
        //All to do with LCD......................
        //-----------------------------------------

        #define PORTB_MASK      0x38   // PB3, PB4, PB5
        #define PORTD_MASK      0x80   // PD7

        #define PORTB_SHIFT      3
        #define PORTD_SHIFT      1

        #define LCD_RW         PD3
        #define LCD_RS         PD2
        #define LCD_E         PD4

        #define   LCD_CLEAR      0x01
        #define   LCD_LINE1      0x02
        #define LCD_LINE2      0xC0
        #define LCD_SHOW_BLINK   0x0F
        #define LCD_SHOW_SOLID   0x0E     
        #define LCD_HIDE      0x0C
        #define LCD_CURSOR_L   0x10
        #define LCD_CURSOR_R   0x14
        #define LCD_SHIFT_L      0x18
        #define LCD_SHIFT_R      0x1C


        void lcd_nibble(unsigned char nibble)
        {

           
           nibble &= 0x0F;

           // Shift our nibble so bits 0, 1, and 2 line up with PB3, PB4,
           // and PB5:

           nibble <<= PORTB_SHIFT;

           // Clear those bits out of PORTB so we can write into them:

           PORTB &= ~PORTB_MASK;

           // And load PORTB with those three bits:

           PORTB |= (nibble & PORTB_MASK);

           // Now shift our nibble so bit 3 lines up with PD7:

           nibble <<= PORTD_SHIFT;

           // Clear that bit out of PORTD so we can write into it:

           PORTD &= ~PORTD_MASK;

           // And load pORTD with that last bit:

           PORTD |= (nibble & PORTD_MASK);

           // Delay for 1ms so the LCD can register it's got the nibble:

           _delay_ms(1);



           PORTD |= (1 << LCD_E);
           asm(
              "nop" "\n\t"
              "nop" "\n\t"
              "nop" "\n\t"
              "nop" "\n\t"
              "nop" "\n\t"
              ::);

           // Bring E low

           PORTD &= ~(1 << LCD_E);

           asm(
              "nop" "\n\t"
              "nop" "\n\t"
              "nop" "\n\t"
              "nop" "\n\t"
              "nop" "\n\t"
              ::);

           // Our nibble has now been sent to the LCD.
        }



        void lcd_send(unsigned char data)
        {
           unsigned char    temp_ddrb, temp_portb,
                       temp_ddrd, temp_portd;

           // Store our port settings

           temp_ddrb = DDRB;
           temp_portb = PORTB;
           temp_ddrd = DDRD;
           temp_portd = PORTD;

           // Set up port I/O to match what the LCD needs

           DDRB |= PORTB_MASK;
           DDRD |= PORTD_MASK;

           // Send the data

           lcd_nibble(data >> 4);   // High nibble first
           lcd_nibble(data);      // Low nibble second

           // Restore our port settings

           DDRD = temp_ddrd;
           PORTD = temp_portd;
           DDRB = temp_ddrb;
           PORTB = temp_portb;
        }



        void lcd_cmd(unsigned char cmd)
        {
           // Hold RW and RS low

           PORTD &= !((1 << LCD_RW) | (1 << LCD_RS));

           // Send the command

           lcd_send(cmd);

           // Delay for 1ms to let the command process

           _delay_ms(1);
        }

        void lcd_data(unsigned char data)
        {
           // Hold RW low

           PORTD &= ~(1 << LCD_RW);

           // Hold RS high

           PORTD |= (1 << LCD_RS);

           // Send the data.  No waits, so we can go right into sending more
           // data.

           lcd_send(data);
        }

        // lcd_string sends a string to the LCD.

        void lcd_string(const unsigned char *str)
        {
           while (*str != 0)
              lcd_data(*str++);
        }

        // lcd_int prints an integer.

        void lcd_int(unsigned char n)
        {
           unsigned char st[4] = {0,0,0,0};

           // The 0x30 addition shifts the decimal number up
           // to the ASCII location of "0".

           // Hundreds place
           st[0] = (n / 100) + 0x30;
           n = n % 100;

           // Tens place
           st[1] = (n / 10) + 0x30;
           n = n % 10;

           // Ones place
           st[2] = n + 0x30;

           // Print it as a string
           lcd_string(st);
        }


        void lcd_time(unsigned long int n)
        {
           unsigned char st[8] = {0,0,0,'.',0,0,0,'s'};

           // The 0x30 addition shifts the decimal number up
           // to the ASCII location of "0".
          
           //100s
           st[0] = (n / 100000) + 0x30;
           n = n % 100000;

           //10s
           st[1] = (n / 10000) + 0x30;
           n = n % 10000;

           // 1s
           st[2] = (n / 1000) + 0x30;
           n = n % 1000;


           // 0.1s
           st[4] = (n / 100) + 0x30;
           n = n % 100;

           // 0.01s
           st[5] = (n / 10) + 0x30;
           n = n % 10;

           // 0.001s
           st[6] = n + 0x30;

           // Print it as a string
           lcd_string(st);
        }


        // lcd_init initializes the LCD and MUST be called prior to
        // every other LCD command.

        void lcd_init(void)
        {

           
           DDRD |= (1 << LCD_RW) | (1 << LCD_RS) | (1 << LCD_E);

           

           _delay_ms(30);
           lcd_cmd(0x30);      // 8-bit mode (wake up!)
           _delay_ms(5);
           lcd_cmd(0x30);      // 8-bit mode (wake up!)
           _delay_ms(1);
           lcd_cmd(0x30);      // 8-bit mode (wake up!)
           _delay_ms(1);
           lcd_cmd(0x32);      // 4-bit mode

           //telling the lcd everything..

           lcd_cmd(0x20);
           lcd_cmd(0x28);      // 4-bit mode, 2-line, 5x8 dots/char
           lcd_cmd(0x08);      // Display off, cursor off, blink off
           lcd_cmd(0x01);      // Clear display
           lcd_cmd(0x0F);      // Display on, cursor on, blink on
           lcd_cmd(0x02);      // Return home
           lcd_cmd(0x01);      // Clear display

           // At this point we're good to go.

        }



        void lcd_moveto(unsigned char line, unsigned char pos)
        {

         

          lcd_cmd((line == 1 ? 0x80 : 0xC0) + pos);
        }



        void lcd_moverel(unsigned char dir, unsigned char num)
        {
           unsigned char cmd;

           cmd = dir ? LCD_CURSOR_R : LCD_CURSOR_L;
           while(num-- > 0)
              lcd_cmd(cmd);
        }



        void lcd_shift(unsigned char dir, unsigned char num)
        {
           unsigned char cmd;

           cmd = dir ? LCD_SHIFT_R : LCD_SHIFT_L;
           while(num-- > 0)
              lcd_cmd(cmd);
        }



        #define lcd_clear()   lcd_cmd(LCD_CLEAR)

        // Move the cursor to the beginning of line 1

        #define lcd_line1() lcd_cmd(LCD_LINE1)

        // Move the cursor to the beginning of line 2

        #define lcd_line2() lcd_cmd(LCD_LINE2)

        // Show the cursor as a blinking block.  (A non-blinking cursor is
        // also available.)

        #define lcd_show() lcd_cmd(LCD_SHOW_BLINK)

        // Hide the cursor.

        #define lcd_hide() lcd_cmd(LCD_HIDE)


        void delay_sec(unsigned char sec)
        {
           unsigned int cycles;


           for(cycles = 0; cycles < (sec * 100); cycles ++)
           {
              _delay_ms(10);
           }
        }



        //-----------------------------------------
        //All to do with LCD......................
        //-----------------------------------------


        //STANDARD MAIN FUNCTION

        int main( void )
        {

          //--------------------------------------
          //LCD STUFF TO GO IN MAIN FUNCTION
          //--------------------------------------
         

               // Make sure all our registers are clear
               DDRB = 0;
               DDRC = 0;
               DDRD = 0;

               PORTB = 0;
               PORTC = 0;
               PORTD = 0;

          // Initialize the LCD
          lcd_init();
          //-----------------------------
          //END MAIN FUNCTION LCD STUFF
          //------------------------------
DDRC &= ~(1 << PC5);  
int timing1, timing2, finaltimer;       
int number = 0;
PORTC|=(1<<PC5); 

         //Timer
         DDRD|=(1<<PD1); //Led output

 TCCR1B|=(1<<CS10);//Set up timer

   for (;;) 
   { 
      // Check timer value in if statement, true when count matches 1/20 of a second 
      if (TCNT1 >= 50000) 
      { 

     number=number+50;
        PORTD ^= (1 << PD1); // Toggle the LED 
  
  
      if(!(PINC&(1<<PC5)))
{
_delay_ms(50);
           
         timing1 = number;
		 }

		 while((PINC&(1<<PC5)))
		 {
		 }
{
_delay_ms(50);
           
         timing2 = number;
		 finaltimer = timing2 - timing1;
         
          lcd_line1();
            lcd_string("time");
         lcd_line2();
            lcd_time(finaltimer);
        //End Timer
         number = 0;

         TCNT1 = 0; // Reset timer value 
      } 
   } 
   }

	}
     



      

        

Hello.

I’d be happy to look over your code if you could present it in a more readable format. For example, it’s tough to figure out what’s going on if your program has several hundred lines of LCD code in it. Please reduce the code to the key section that is giving you problems. For example, you could have just posted your main and I could have guessed what lcd_string(“time”) would do without actually seeing the code for the lcd_string() function. Now if your problem involved getting the LCD to work right, then we’d want to see the LCD code. However, if your LCD is fine, putting that code in there just makes it harder for the rest of us to figure out what is relevant.

Also, it would be helpful to me if you could format the code so the indentations are consistent and appropriate. It’s difficult to read when somethings are randomly tabbed.

- Ben

Ta Ben, OK, I have lined up brackets etc, and here is the piece of code causing problems:

DDRC &= ~(1 << PC5);  
int timing1, timing2, finaltimer;       
int number = 0;
PORTC|=(1<<PC5); 

         //Timer
        DDRD|=(1<<PD1); //Led output

 		TCCR1B|=(1<<CS10);//Set up timer

   for (;;) 
   { 
      // Check timer value in if statement, true when count matches 1/20 of a second 
      if (TCNT1 >= 1000) 
      { 

     	number++//number to display in time format
        PORTD ^= (1 << PD1); // Toggle the LED 
  
  
      		if(!(PINC&(1<<PC5)))//if button pressed, dlay 50(for debouncing)
			{
			_delay_ms(50);
           
         	timing1 = number;//then record time button was pressed
		 	}
//this is where i am struggling (if above is correct). I dont know how to contiue the clock and record when the next 
//button press occurs, then get lcd to display that time.

		 		while((PINC&(1<<PC5)))
		 		{
		 		}
					{
					_delay_ms(50);
           
         			timing2 = number;
		 			finaltimer = timing2 - timing1;
         
          			lcd_line1();
           			lcd_string("time");
         			lcd_line2();
           			lcd_time(finaltimer);
        			//End Timer
         			number = 0;

         			TCNT1 = 0; // Reset timer value 
      		} 
   		} 
   }

I want the counter to have a resolution of 1ms, so that when a push button is pressed twice, i can record and display the time between them. THe code above is the part that does this, but it doesnt tick over every millisecond. I know this because if i time the time between the two presses, the mega8 is not even close.

I think you might have an easier time if you use timer interrupts to update your millisecond counter, but before going into that, let’s see if you can get your current approach to work. I will suggest in pseudocode how I would approach this problem, and for the moment I’ll leave the implementation details to you.

As a first step, let’s make sure you know your Orangutan’s clock speed. I suggest you make a simple led-blinking program where the LED should blink once per second. If your Orangutan’s clock is a different speed from what you expect (you say it’s 1 MHz, right?), the blink speed will be clearly wrong:

int main()
{
    DDRD |= (1 << PD1);  // led pin -> output
    TCCR1B = (1 << CS10);  // timer1 is using the system clock (should be 1 MHz)
    TCNT1 = 0;

    unsigned int time_ms = 0;  // number of milliseconds that have elapsed

    while (1)
    {
        if (TCNT1 >= 1000)
        {
            TCNT1 = 0;
            time_ms++;
        }

        if (time_ms >= 500)
        {
            time_ms = 0;
            PORTD ^= (1 << PD1);  // toggle the LED state every 500 ms (i.e. blink period = 1 s) 
        }
    }

    return 0;
}

If I haven’t made a mistake and if your Orangutan is running at 1 MHz, you should see the LED blink every second. If your Orangutan is running at 8 MHz, the blink rate should be eight times faster than this. Knowing your clock speed is crucial if you want to make an accurate timing application.

Once you have verified your clock speed, I would suggest implementing something like the following algorithm:

initialize things (timer, I/O pins)
while (button not pressed)  // loop here until first button press
  ;

while (1)  // loop forever
{
  _delay_ms(10);  // delay for 10 ms to debounce the press
  while (button is pressed)  // loop here until button is released
    ;
  _delay_ms(10);  // delay for 10 ms to debounce button release

  time_ms = 0;  // here is where we start timing
  TCNT1 = 0;

  while (button not pressed)  // loop here until second button press
  {
    // while waiting for button press, keep track of elapsed time
    //  if system clock is 1 MHz, 1000 TCNT1 ticks = 1 ms
    //  if system clock is 8 MHz, 8000 TCNT1 ticks = 1 ms
    if (TCNT1 >= 1000)
    {
      time_ms++;
      TCNT1 = 0;
    }
  }

  lcd_time(time_ms);  // display the time
}

Does this pseudocode make sense to you? Is it clear what I’m suggesting and why I’m suggesting it?

- Ben

Hi Ben, its all good, and everything, but I have a concern…

I tried to add a second time count, more out of interest really, so that once it has a reading for the first it would then take abother (see below), but It still only gets to the end of the first before displaying on the screen, is it a deboucning problem?

while (!(PINC&(1<<PC5)))  // loop here until button is released
    ;
  _delay_ms(10);  // delay for 10 ms to debounce button release

  time_ms1 = 0;  // here is where we start timing
  time_ms2 = 0;
  TCNT1 = 0;

  while (PINC&(1<<PC5))  // loop here until second button press
  {
  
    // while waiting for button press, keep track of elapsed time
    //  if system clock is 1 MHz, 1000 TCNT1 ticks = 1 ms
    //  if system clock is 8 MHz, 8000 TCNT1 ticks = 1 ms
    if (TCNT1 >= 1000)
    {
      time_ms1++;
      TCNT1 = 0;
    }
  }
  TCNT1 = 0;
while (PINC&(1<<PC5))  // loop here until 3rd button press
  {
  
    // while waiting for button press, keep track of elapsed time
    //  if system clock is 1 MHz, 1000 TCNT1 ticks = 1 ms
    //  if system clock is 8 MHz, 8000 TCNT1 ticks = 1 ms
    if (TCNT1 >= 1000)
    {
      time_ms2++;
      TCNT1 = 0;
    }
  }

It looks like it’s not a switch-bounce problem, but just the fact that there is no instruction to wait for the button to be released after the second press.

The first while (PINC&(1<<PC5)) loop ends when you press a button, because the fifth bit of PINC is low, and the argument of the while loop is false. Your code then immediately proceeds to the second while (PINC&(1<<PC5)) loop, and the odds are pretty good that you’re still holding the button a few microseconds after you pressed it, so this second loop gets completely skipped over. A quick way to deal with this would be to also copy the “loop here until button is released” line in between your two counting loops. Then you’ll start having switch-bounce problems, so you’ll want to copy the debounce delay as well:

while (!(PINC&(1<<PC5)))  // loop here until button is released
	;
  _delay_ms(10);  // delay for 10 ms to debounce button release

  time_ms1 = 0;  // here is where we start timing
  time_ms2 = 0;
  TCNT1 = 0;

  while (PINC&(1<<PC5))  // loop here until second button press
  {
 
	// while waiting for button press, keep track of elapsed time
	//  if system clock is 1 MHz, 1000 TCNT1 ticks = 1 ms
	//  if system clock is 8 MHz, 8000 TCNT1 ticks = 1 ms
	if (TCNT1 >= 1000)
	{
	  time_ms1++;
	  TCNT1 = 0;
	}
  }
  
  while (!(PINC&(1<<PC5)))  // loop here until button is released
	;
  _delay_ms(10);  // delay for 10 ms to debounce button release
  
  TCNT1 = 0;
while (PINC&(1<<PC5))  // loop here until 3rd button press
  {
 
	// while waiting for button press, keep track of elapsed time
	//  if system clock is 1 MHz, 1000 TCNT1 ticks = 1 ms
	//  if system clock is 8 MHz, 8000 TCNT1 ticks = 1 ms
	if (TCNT1 >= 1000)
	{
	  time_ms2++;
	  TCNT1 = 0;
	}
  }

This code will stop the first counting loop when you press the button, but it won’t start the second counting loop until you release the button. You could start both your counting variables at 10ms to account for the debounce delay, but as Ben said before, if you want to do real stopwatch-style lap counting, Interrupts are going to make things much simpler.

So, any chance of getting your hard drive or it’s contents back from Dell?

-Adam

P.S. Sorry to disappear all of a sudden last week, I had a baby niece born a week early and made a couple of middle of the night round trips between Michigan and Iowa. Everyone is doing great!

I might have set up my code wrong, but the way I intended it to work was to time from one button press to the next. Therefore:

  1. Push a button to start the program
  2. Push a button again to get the time between the first two button presses
  3. Push a button again to get the time between presses 2 and 3
  4. Push a button again to get the time between presses 3 and 4
    etc

You shouldn’t have needed to add anything extra to get it to time a third button press (unless you wanted to wait to update the display with two values rather than sequentially updating it).

- Ben

P.S. Congratulations on your new niece, Adam!

Yes congratulations on your niece! Very nice to hear everyones well.

That did work - although its hard to test it with my make shift jumper wires instead of a real push switch! I think i need to make a small investment…

Interupts:

As far as i understand, (i read this about keyboards once) with interupts, the computer continues its normal routine, until something is pressed, at that moment, it is like calling a new function, and the computer jumps to a new place in the code to find out what to do, does it, and goes back to normal. I might be completely wrong. Although if this is the case, then it would explain my constant confusion as to how, if code is read in order the computer knows what to do when a different button is pressed to the next one in the code.

Are interupts just if’s and elses, or do they have a seperate sub heading…

I have emailed smileymicros for a new download link - maybe they’ll give it to me?

To a.mlw.walker:

You really need to think through what each section and every single line of code is doing, or you will be spending most of your time trying to debug all sorts of problems unrelated to the simple timing issues.

A couple of points jumped out at me while glancing through your code:

  1. The very first priority is to determine or set your CPU clock speed. The F_CPU parameter is nonfunctional in your original posting, because the #define is preceded by //. Without the correct F_CPU (which might be set elsewhere in your development system), the library _delay_ routines won’t work correctly. There are also strict limitations on the delays that can be produced by these routines, and that in turn depends on the clock speed.

  2. The subroutine lcd_time as defined in your original post won’t work reliably. This is because the string has no terminating zero byte. The string display routine will cruise through memory, sending bytes to the LCD, looking for that zero. Unless you are extremely lucky, total garbage will appear on the display.

  3. Accurate timing can’t be done in a main loop because you have no way of knowing what the overhead is in reading the timer and resetting it to zero. You certainly can’t make a clock this way. For accurate timing of a button press, you need to use interrupts for both the timer and the button press, which have their own (very small) overhead. The ringtone routine that I just posted contains an example of an interrupt-driven timer that accurately counts 8.192 ms intervals and another to produce arbitrary but accurate audio tones. It is easy to modify either routine to produce any desired clock tick, and this takes about 8 lines of code, total. There are examples of button press or pin change interrupts in this forum as well. One example would be the code that I posted to measure RPM’s.

It is always a great idea to study working code examples – any that you can find!

Good luck, Jim

Thanks Jim, I turned the F_CPU off a few days ago to try and diagnose why i wasnt getting “real” time timing - then forgot to turn it back on again…

I do get alot of rubbish on the screen, sometimes, and it takes a few on/offs to sort it, I thought it was kinda normal, but still although you explained, I’m not really sure how to sort it :C

I had a look through your ringtone_short and long, I couldnt find the 8 lines you refer to - that tick a timer over at 8.192 second intervals.

In your ringtone player, your interupts are setup as if elses?

So i could say
if (button PC5 pressed)
{
do…}
}
else if (button PC4 pressed)
{
do…
}
and keep going like that. And that is interupts?

This post probably sounds quite ignorant sorry about that!

Strings in C must end with a byte containing a zero. Follow the example in lcd_int() and make the string array an extra byte longer, containing that zero. For example:

  void lcd_time(unsigned long int n)
        {
           unsigned char st[8] = {0,0,0,'.',0,0,0,'s',0};

Then, you need to learn what an interrupt is. This is an event within the CPU that automatically causes a special type of subroutine to be called (an interrupt service routine), “stealing” some time from the main program. Interrupts are more or less essential for microcontrollers to operate in the real world, which is their purpose. There are some great tutorials on AVRFreaks, for example one describes how to use interrupts to receive a character when it arrives at the serial port. See avrfreaks.net/index.php?name … ic&t=48188

Another example is a button press. You can use a special external interrupt pin (INT0 or INT1, not easily accessible on the Orangutan) or a pin change interrupt on certain ports (e.g. PORTC). Here is a reasonable tutorial: windmeadow.com/node/19

The following is an untested outline of a program that times intervals with 1 millisecond accuracy. In this case the interrupt service routine increments a global timer variable once every millisecond. Does anything look familiar when compared to the ringtone programs? You need to fill in the “something” and decide what to do with the resulting time. The counter timer_ticks overflows every 65.536 seconds, so the event you are timing can’t take longer than that, unless you modify the interrupt routine. This is simple to do and I encourage you to figure out how to do that! If you are using WinAVR, refer to the avrlibc documentation for more information on interrupt programming.

#define F_CPU 8000000UL 
#include <avr/io.h>
#include <avr/interrupt.h>

volatile unsigned int timer_tick;		//global timer variable MUST BE DECLARED VOLATILE

int main(void)
{
	unsigned int milliseconds;		//variable to save elapsed time

//initialize timer 0
	TCCR0A = 0;
	TCCR0B = (1<<CS01)|(1<<CS00);	//set timer to normal mode with CPU clock/64 (125 kHz)
	TCNT0 = ~(125);			        //preload counter to overflow after 1 ms (125 counts)
	TIMSK0 = (1<<TOIE0);		     //enable interrupt on timer0 overflow

	sei();					    //enable all interrupts

// time an event:
	timer_tick=0;				//start timer, clear global millisecond counter
//wait for event
	while ( !something )  { };		// wait until something (could be button press) is no longer zero
	milliseconds = timer_tick;		//get the value of the millisecond counter

// continue with program
...
}

/*
TIMER 0 overflow interrupt service routine
This routine is called every millisecond
Operation: increment global variable timer_tick, reload TCNT0 to count 125 clock cycles
*/

ISR(TIMER0_OVF_vect)
{
   timer_tick++;
   TCNT0 = ~(125);			//preload counter to overflow after 1 ms (125 counts)
}

The best way to learn coding is to carefully read a bunch of working examples. Absolutely everything counts! Also unfortunately, you must learn how to read the atmega168 (or whichever is relevant) data sheet.

Hope this helps,
Jim

Cool, So I have added something into the something box, actually quite alot:

DDRC &= ~(1 << PC5);
PORTC|=(1<<PC5);  


unsigned int time_ms1 = 0;
unsigned int time_ms2 = 0;
  


  
while (PINC&(1<<PC5))  // loop here until first button press
  ;
//initialize timer 0
   TCCR0A = 0;
   TCCR0B = (1<<CS01)|(1<<CS00);   //set timer to normal mode with CPU clock/64 (125 kHz)
   TCNT0 = ~(125);                 //preload counter to overflow after 1 ms (125 counts)
   TIMSK0 = (1<<TOIE0);           //enable interrupt on timer0 overflow 
while (1)  // loop forever
{
  _delay_ms(10);  // delay for 10 ms to debounce the press

  while (!(PINC&(1<<PC5)))  // loop here until button is released
   ;
  _delay_ms(10);  // delay for 10 ms to debounce button release

  time_ms1 = 0;  // here is where we start timing
  time_ms2 = 0;
  TCNT0 = 10;

 
  while (PINC&(1<<PC5))  // loop here until second button press
  {

  time_ms1 = timer_tick;      //get the value of the millisecond counter
    
   }
  }
  
  while (!(PINC&(1<<PC5)))  // loop here until button is released
   ;
  _delay_ms(10);  // delay for 10 ms to debounce button release
  
  TCNT0 = 10;
while (PINC&(1<<PC5))  // loop here until 3rd button press
  {
 time_ms2 = timer_tick;      //get the value of the millisecond counter

  }
  lcd_line1();// display the times
  lcd_time(time_ms1);
  lcd_line2();
  lcd_time(time_ms2);  
}
}
     





/*
TIMER 0 overflow interrupt service routine
This routine is called every millisecond
Operation: increment global variable timer_tick, reload TCNT0 to count 125 clock cycles
*/

ISR(TIMER0_OVF_vect)
{
   timer_tick++;
   TCNT0 = ~(125);         //preload counter to overflow after 1 ms (125 counts)
}

The errors though say TCCR0A, TCCR0B and TIMSK0 undeclared, i thought that was what initialising the timer did?

i therefore dont know if my bit i added in is correct beacuse of these errors, and whether i have used the function to tick over the timer correctly

Jim’s code is written to run on the ATMega168 based Orangutan.

The hardware is similar, but the register names are a little different on the ATMega8 MCU on your Orangutan. Try this code for the timer initialization:

//initialize timer 0
TCNT0=~(125);//preload counter to overflow after 1 ms (125 counts)
TIMSK=(1<<TOIE0);//enable interrupt on timer0 overflow
TCCR0=(1<<CS01)|(1<<CS00);//set timer to normal mode with CPU clock/64 (125 kHz)

sei();//enable all interrupts

You need to make sure to include the “sei()” command in your code. It enables interrupts globally, and without it the timer0 overflow interrupt won’t work. It sounds like you did this already or your compiler would be showing more errors, but you do need to include the interrupt header “<avr/interrupt.h>”. Also, for the timing of this code to work out right, you will need your Orangutan running at 8MHz, not 1MHz like you were using before, so you’ll want to make sure to uncheck the “divide by 8 internally” fuse if you haven’t already.

-Adam

TCCR0A etc. are the names of registers that control timer/counter 0. If they are undeclared, you have forgotten to #include <avr/io.h> or you have defined the wrong processor. The code that I posted was for the atmega168. Check the data sheets for the atmega168 to see what those lines of code are actually doing and to determine what you must do for a different processor. The code will work on the atmega8, but the register names are slightly different and must be changed. For example, on the atmega8 there is only one TCCR0 register, rather than two named TCCR0A and TCCR0B.

If you want to make this work, you simply cannot avoid learning how to read the data sheet! It will be worth it in the long run.

BTW there are a couple of minor errors in the timing code that I posted. The TCNT0 constant should be ~(124), which is the negative, or the “two’s complement”, of 125. Also, just after timer_tick is set to zero in the main program, there should be another line to reset TCNT0, i.e.

timer_tick=0;
TCNT0 = ~(124);

Finally, there is a better way to create a millisecond timer that will also be more accurate. That would be to use CTC mode (clear timer on compare match). In CTC mode the timer will automatically reset itself to zero when it reaches 125. This avoids the small delay of resetting the timer register TCNT0 in the interrupt service routine, which in turn causes it to be a few microseconds slow every millisecond. I challenge you to figure out how to make these changes! The data sheet will come in very handy for this.

Regards, Jim

Hey guys again, so I have downloaded the atmega8 datasheet, and had a scan through. Did a search but couldnt find anything on CTC, does it have another name, or something else i should search for?

Aside from that, Here is my code, it what should be pretty complete form, but the results arent being displayed on the lcd. Have i just got the display part in the wrong place or is there a bigger mistake i havent noticed?

thanks
alex

//more of main function here...
DDRC &= ~(1 << PC5);
PORTC|=(1<<PC5);  


unsigned int time_ms1 = 0;
unsigned int time_ms2 = 0;
  


  
while (PINC&(1<<PC5))  // loop here until first button press
  ;
//initialize timer 0
TCNT0=~(124);//preload counter to overflow after 1 ms (125 counts)
TIMSK=(1<<TOIE0);//enable interrupt on timer0 overflow
TCCR0=(1<<CS01)|(1<<CS00);//set timer to normal mode with CPU clock/64 (125 kHz)

sei();//enable all interrupts

// time an event:
   timer_tick=0;            //start timer, clear global millisecond counter
TCNT0 = ~(124);

while (1)  // loop forever
{
  _delay_ms(10);  // delay for 10 ms to debounce the press

  while (!(PINC&(1<<PC5)))  // loop here until button is released
   ;
  _delay_ms(10);  // delay for 10 ms to debounce button release

  time_ms1 = 0;  // here is where we start timing
  time_ms2 = 0;
  TCNT0 = 10;

 
  while (PINC&(1<<PC5))  // loop here until second button press
  {    
   }
   time_ms1 = timer_tick;      //get the value of the millisecond counter
  }
  
  while (!(PINC&(1<<PC5)))  // loop here until button is released
   ;
  _delay_ms(10);  // delay for 10 ms to debounce button release
  
  TCNT0 = 10;
while (PINC&(1<<PC5))  // loop here until 3rd button press
  {
  }
 time_ms2 = timer_tick;      //get the value of the millisecond counter

  
  lcd_line1();// display the times
  lcd_time(time_ms1);
  lcd_line2();
  lcd_time(time_ms2);  
}

     





/*
TIMER 0 overflow interrupt service routine
This routine is called every millisecond
Operation: increment global variable timer_tick, reload TCNT0 to count 125 clock cycles
*/

ISR(TIMER0_OVF_vect)
{
   timer_tick++;
   TCNT0 = ~(125);         //preload counter to overflow after 1 ms (125 counts)
}

alex:

On the atmega8, timers 1 and 2 offer CTC mode. My ringtone programs use timer1 in CTC mode to generate accurate audio frequencies. See page 111 on the atmega8 data sheet for an overview of using timer 2 in this mode. See also “Newbie’s Guide to Timers” on AVRFreaks avrfreaks.net/index.php?name … 06&start=0

With regard to your code, if nothing is displayed on the LCD, it seems likely that you are not executing the part of the program that would display something.

Here are some hints regarding a few, but not all, of the difficulties in your code.

  1. Follow your brackets to determine how the while(1) construction actually loops.

  2. Have you carefully thought through what the following construction accomplishes?

while (PINC&(1<<PC5))  // loop here until second button press
  {   
   }
   time_ms1 = timer_tick;      //get the value of the millisecond counter
  1. Why do you set TCNT0=10 ?

By not paying attention, you cause yourself no end of trouble. Considering just the ease with which one can make careless mistakes, the C language is one of the worst there is.

Regards, Jim

hey,

tcn0 is set to ten to overcome delay due to button bouncing.

that oiece of code does nothing until the button is pressed?

Hi Guys, been trying to sort a breadbard crisis out…

this piece of code seems pretty accrate as a timer between button presses, i struggled with the interupts method, but what is the innacuracy quantatively of this method?

The timing inaccuracy of your current method isn’t really noticeable, it’s much more of a programming style issue. With delay-based timing your microcontroller is essentially paralyzed during the delays, which makes it very difficult when you want to start doing multiple things at once. Using your microcontrollers peripheral hardware (timers, interrupts) frees up the microcontroller to do useful things while it’s waiting around, as well as accounting for the extra time it takes to do things like check button states or write to the LCD.

The real thing that makes your stopwatch inaccurate, by orders of magnitude more than delay-based code, is the performance of the internal RC oscillator. The ATMega8’s oscillator is only accurate to within 3%, and that’s at 25 degrees Celsius, and 5V input. Thats plus or minus 1.8 seconds every minute, or imagine if your wrist watch drifted by as much as 43 minutes every day! The frequency wanders with time, and can go outside that margin at different temperatures and input voltages. If you wanted to do real precision timing over periods longer than a few seconds you would need to hook up an external crystal oscillator, like the kind digital watches use.

I guess the point is it’s not so much about the timing, it’s about figuring out how to make the most of the hardware. Then again, you can do a heck of a lot with just delays.

-Adam

those all sound good points, and my next question was going to be how to do multiple things at once…

When i saw though an interupt method:

while (PINC&(1<<PC5))  // loop here until first button press
  ;
//initialize timer 0
TCNT0=~(124);//preload counter to overflow after 1 ms (125 counts)
TIMSK=(1<<TOIE0);//enable interrupt on timer0 overflow
TCCR0=(1<<CS01)|(1<<CS00);//set timer to normal mode with CPU clock/64 (125 kHz)

sei();//enable all interrupts

// time an event:
   timer_tick=0;            //start timer, clear global millisecond counter
TCNT0 = ~(124);

while (1)  // loop forever
{
  _delay_ms(10);  // delay for 10 ms to debounce the press

  while (!(PINC&(1<<PC5)))  // loop here until button is released
   ;
  _delay_ms(10);  // delay for 10 ms to debounce button release

  time_ms1 = 0;  // here is where we start timing
  time_ms2 = 0;
  TCNT0 = 10;

 
  while (PINC&(1<<PC5))  // loop here until second button press
  {    
   }
   time_ms1 = timer_tick;      //get the value of the millisecond counter
  }
  
  while (!(PINC&(1<<PC5)))  // loop here until button is released
   ;
  _delay_ms(10);  // delay for 10 ms to debounce button release
  
  TCNT0 = 10;
while (PINC&(1<<PC5))  // loop here until 3rd button press
  {
  }
 time_ms2 = timer_tick;      //get the value of the millisecond counter

  
  lcd_line1();// display the times
  lcd_time(time_ms1);
  lcd_line2();
  lcd_time(time_ms2);  
}

     





/*
TIMER 0 overflow interrupt service routine
This routine is called every millisecond
Operation: increment global variable timer_tick, reload TCNT0 to count 125 clock cycles
*/

ISR(TIMER0_OVF_vect)
{
   timer_tick++;
   TCNT0 = ~(125);         //preload counter to overflow after 1 ms (125 counts)
}

I couldnt get it to work the same way, as i have just done with delays. If the above code is using interupts properly, and I managed to somehow get it to work so that it would time between two button presses, could i then do other things at the same time?

Can you see the mistake in my code above?