March 16, 2011
by Linkster
|
Can anyone recommend a internal timer to use for the ATmega168 that meets the following criteria?
1. Runs in the background and doesn't cause any pauses or delays in the program while running or firing a interrupt.
2. Can be used to toggle the output of a MCU pin, (maybe two pins).
3. Be able to set the time between pulses up to 4 minutes. But yet’
4. Be able to have two 5 ms pulses within 5 Milliseconds of each other. Actually, I'm pulling PC0 and PC2 to ground for 5ms twice with a 5ms pause between. Then waiting 240 seconds and repeating.
(p5 means(5ms pulse).
-----|-p5-|-5-|-p5-|-----240sec-----|-p5-|-5-|-p5-|-------240sec----->repeat forever.
I've tried TIMER0_OVERFLOW and got pulses but this type of clock seems to be missing a few requirements. I looked at Mikes water servo but can't get my head around setting up the timer for what I want. I'm pretty much lost on timers and setting them up.
I appreciate any help.
Gary |
March 16, 2011
by bretm
|
No such thing. You can have a timer that toggles pins without any pauses or delays in the program, but not with the delays you're talking about. Since the MCU can physically only run one instruction at a time, firing an interrupt will always interrupt the main program flow for the duration of the interrupt. But the interruptions can be very short.
Why requirement #1? You can't tolerate any pauses at all? How accurate does the timing need to be, e.g. if the pulse is 1 microsecond early or late does it matter? 10us? 100us? 1ms? |
March 16, 2011
by Linkster
|
I think I'm getting a better picture of the timer I want.
8bit timer/counter0 with PWM with CTC Mode or Counter Timer on Compare, I set the top value with OCR0A then set the OCF0A flags to generate a output and will also allow me to update the top value each time a flag is reached. Then set the OC0A to toggle a logic level of a pin(s) (as long as the pin(s) are set as an output) on each compare by setting COM0A1:0 to 1.
You know it all sounds good on paper untill you cycle the on/off switch.
I'm still lost.
Gary |
March 16, 2011
by Linkster
|
Hi bretm,
I guess if the interrupt is short like say two pulses within 15ms, it should hardly be noticable. I'm good with that. The Timer0/Clock_Overflow was stalling the program but I'm sure it was the way I was setting it.
Maybe microseconds would be fine if I can be sure that the output will in fact be noticed by the digital caliper. Like I said it might be the way that I set up the first timer. I tried lower than 5ms and it didn't get noticed by the caliper and yet my logic probe indicated the pulse.
I'm listening. |
March 16, 2011
by Noter
|
You can set one of the timers to generate 1ms interrupts and then count them. When a the count is 240000 that would be 4 minutes. Then some count of 5 bursts with whatever pin action you want and back to waiting for the 240000 again. You would put this logic in the interrupt routine and it would run 1000 times a second. Use a long integer to count because it's 32 bit and big enough to hold 240000. |
March 16, 2011
by bretm
|
Everything happens in 5ms increments. The pulses, the 5ms pauses, even the 240s pause is just 5ms times 48000. So the first thing you'll want to do is set up a timer that causes an interrupt to happen every 5ms.
Can you get that far? If you had a 5ms interrupt could you figure out the rest? |
March 16, 2011
by bretm
|
5ms will be easier than 1ms because a) 1ms involves a fractional number of clock cycles with a 14.7456MHz clock but 5ms doesn't, and b) this will let him use a 16-bit variable to keep track of 48003 states instead of 32 bits to keep track of 240015 states. |
March 16, 2011
by bretm
|
Hint: 5ms is 73728 clock cycles (using the NerdKits crystal). This is 72 x 1024. |
March 16, 2011
by Noter
|
I see, that makes good sense - thanks. |
March 16, 2011
by Linkster
|
"Can you get that far?"
I'm trying to get my head around it but I got a clearer picture on what needs to happen.
I get the 2400015 staes but got lost on 48003.
No, I don't know how to set this up yet.
Gary |
March 16, 2011
by Linkster
|
Duh!
I get the 48,000.
I'm losing my vision from reading the 168 pdf. |
March 16, 2011
by bretm
|
Yeah, 240s + 15ms equals 240.015s, which is 48003 times 5ms. So at tick=0 and tick=2 you pull the pins low, at tick=1 and tick=3 pull them high, and tick=48002 set the tick counter to -1 so that when it increments it goes back to 0. All other values do nothing. (I'd use a switch statement.) Then increment the tick counter.
(Note that going up to 48003 requires using an unsigned int, so -1 doesn't seem like it would work but it should. It "wraps around".) |
March 16, 2011
by Linkster
|
bretm,
Thanks for getting me going in a positive direction.
I'll work on this more tomorow. I'm too old to be up this late.
But more questions first.
Is tick part of a timer or counter setting in the registers?
Am I using the timer/counter I mentioned in the third posts?
These are dumb questions I know but I'll get there with a little help.
Thanks
Gary |
March 17, 2011
by bretm
|
You can use any of the three timers but Timer0 using CTC mode, like you described, would work great. Prescaler set to clk/1024, OC0A set to 71.
Tick is just the name of a global variable that you would need to declare as "volatile uint16_t". It doesn't have too be called tick. You can call it state or whatever you want. What you'll end up with is called a "state machine" which is a useful way of solving a lot of programming problems. |
March 17, 2011
by Linkster
|
Am I getting close or totally lost? I looked at Mikes water servo timer and based it off your info and that timer. I think I need a nother hint. (or two)
Here is what I got so far with only one error on line 46. But no timer happening.
Don't luagh to hard.
I'm listening,
Gary
#define F_CPU 14745600
#include <stdio.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <inttypes.h>
#include "../libnerdkits/delay.h"
#include "../libnerdkits/lcd.h"
#include "../libnerdkits/uart.h"
/*PC0 Pulse indicator for caliper sleep timer-RED LED
PC5 Clock indicator YELLOW LED
PC3 Data indicator GREEN LED
PC1 ALARM RED LED
PB3 power to caliper
PB1 DATA
PB2 CLOCK
*/
////////////////////////////interrupt for Caliper Sleep timer////////////////
#define tick_0 0
#define tick_1 1
#define tick_2 2
#define tick_3 3
#define tick_reset 48002
void tick_set(uint16_t x) {
OCR0B = x;
}
volatile uint16_t OC0A;
void unint16_t timer0 () {
OCR0A = 48003;
OC0A = 71;
TCCR0A = (1<<COM0B1) | (1<<WGM01) | (1<<WGM00);
TCCR0B = (1<<WGM13) | (1<<WGM12) | (1<<CS11);
// set outputs
// Toggle PORTC PC0 Red LED and PC2 pin to reset caliper sleep timer
DDRC |= (1<<PC0) | (1<<PC2);
if(tick_0) {
PORTC &=~ (1<<PC0) | (1<<PC2);
}
if(tick_1) {
PORTC |= (1<<PC0) | (1<<PC2);
}
if(tick_2) {
PORTC &=~ (1<<PC0) | (1<<PC2);
}
if(tick_3) {
PORTC |= (1<<PC0) | (1<<PC2);
}
if (tick_reset){
(OCR1A = -1 ); }
}
|
March 17, 2011
by bretm
|
As you suspected, there are a couple of problems with this.
You program needs a routine called "main". The compiler requires this. That's how it knows where to start.
You also need a routine with the header "ISR(TIMER0_COMPA_vect)". That tells the compiler what statements to execute when the Timer0 comparison A match happens.
Before the ISR you need "volatile uint16_t tick;" or whatever you want to call your state variable. It shouldn't be called anything like OC0A since it's not directly related to the timer hardware. It's just a software thing of your own devising.
Main should initialize the timer hardware, enable interrupts, and then start an infinite loop that does nothing. The loop is to prevent main from completing. If it completes, the interrupts will become disabled and everything will stop. Later, when your program does more than just the pulses, that's where you'll add most of the program logic.
int main()
{
// set up timer
// enable interrupts
// loop forever doing nothing (for now)
}
I'll let you fill in those blanks.
Setting up the timer involves initializing TCCR0A and B like you did, setting OCR0A to 71, and setting your "tick" variable to 0. You also need to set the TIMSK0 register to turn on timer interrupts.
In TCCR0A you don't want to set WGM00. When WGM00 and 01 are both set, it enables the Fast PWM mode. You just want CTC mode, which needs WGM01 but not WGM00 set.
TCCR0B does not contain fields WGS13, WGS13, or CS11. Those are related to Timer/Counter1, not Timer/Counter0. Instead you'll want to set CS00 and CS02 to use the clk/1024 prescaler.
Enabling interrupts is done simply by calling the "sei" function.
Looping forever can be done with "while", "do", "for", or "goto". I usually use "while".
The ISR should examine the "tick" variable and take the appropriate action based on its current value, and then it should increment the "tick" variable.
The way the "if" statement works is that it executes the subsequent statement or block if the expression within the (...) is non-zero. So when you say "if (tick_1) {...}" you're saying "if (1) {...}" which causes the "..." to always be executed for tick_1, tick_2, tick_3, tick_reset because they're all non-zero, and never be executed for tick_0 because it's zero. What you really need to be doing is a comparison using the "==" equality operator, such as "if (tick == tick_0) {...}" where "tick" is a variable that you increment every 5ms.
But I would actually use a "switch" statement instead. That's what you want to use whenever you find that you have a series of "if" statements all doing comparisons with the same variable over and over again.
switch (*variableNameGoesHere*)
{
case *first number here*:
case *another number here*:
// one or more statements
break;
case *another number here*:
case *another number here*:
// one or more statements
break;
case *another number here*:
// one or more statements
break;
}
|
March 17, 2011
by Linkster
|
bretm,
I had seen that the water servo's timer was setup before the main and then initiated inside the main by calling pwm_init(); // init PWM.
I have a main but I only posted the timer portion. I'll carfully study your post and see if I can figure some of this out.
Thanks for putting up with me,
Gary
|
March 17, 2011
by bretm
|
Putting the initialization code into a separate routine makes it a little easier to read and makes it slightly easier to copy and reuse that code in another program. But otherwise it's a matter of taste. |
March 17, 2011
by Linkster
|
Made changes but still not seeing it. It loads and the LED lights but thats about it.
//Timer0 lesson
//for Atmega 168
#define F_CPU 14745600
#include <stdio.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <inttypes.h>
#include "../libnerdkits/delay.h"
#include "../libnerdkits/lcd.h"
#include "../libnerdkits/uart.h"
// PC0 = red LED
// PC2 = caliper sleep timer reset.
void tick_B (uint16_t x) {
OCR0B = x;
}
volatile uint16_t tick;
ISR(TIMER0_COMPA_vect){
}
int main()
{
tick= 0;
OCR0A= 71;
tick++;
TCCR0A = (1<<COM0B1) | (1<<WGM01)|(1<<CS10);
TCCR0B = (1<<CS00) | (1<<CS01);
sei();
while(1){
#define tick_0 0
#define tick_1 1
#define tick_2 2
#define tick_3 3
#define tick_reset 48002
DDRC |= (1<<PC0) | (1<<PC2);
switch (tick){
case 0:
if(tick_0) {
PORTC &=~ (1<<PC0) | (1<<PC2);
}
break;
case 1:
if(tick_1) {
PORTC |= (1<<PC0) | (1<<PC2);
}
break;
case 2:
if(tick_2) {
PORTC &=~ (1<<PC0) | (1<<PC2);
}
break;
case 3:
if(tick_3) {
PORTC |= (1<<PC0) | (1<<PC2);
}
break;
case tick_reset:
if (tick_reset){
(OCR1A = -1 );
}
break;
// init lcd
lcd_init();
FILE lcd_stream = FDEV_SETUP_STREAM(lcd_putchar, 0, _FDEV_SETUP_WRITE);
lcd_home();
// init serial port
uart_init();
FILE uart_stream = FDEV_SETUP_STREAM(uart_putchar, uart_getchar, _FDEV_SETUP_RW);
stdin = stdout = &uart_stream;
lcd_line_one();
fprintf_P(&lcd_stream,
PSTR(" Timer Lesson "));
}
}
return 0;
}
|
March 17, 2011
by Linkster
|
oh ya! I added tick++; and that is what turned the LED on.
Did I forget the 48003?? yep. If tick is zero, where do I put the top count? |
March 17, 2011
by Linkster
|
This is where I'm at. Cleaned up a little on the "states". No LED no display and most likely no timer. Feel free to rip it apart.
I got to get some dinner in me.
//Timer0 lesson
//for Atmega 168
#define F_CPU 14745600
#include <stdio.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <inttypes.h>
#include "../libnerdkits/delay.h"
#include "../libnerdkits/lcd.h"
#include "../libnerdkits/uart.h"
// PC0 = red LED
// PC2 = caliper sleep timer reset.
void tick_B (uint16_t x) {
OCR0B = x;
}
volatile uint16_t tick;
ISR(TIMER0_COMPA_vect){
}
uint16_t intrpt;
int main()
{
tick= 0;
OCR0A= 71;
TCCR0A = (1<<COM0A1) | (1<<WGM01)|(1<<CS10);
TCCR0B = (1<<CS00) | (1<<CS01);
while(1){
tick++;
sei();
#define tick_0 0
#define tick_1 1
#define tick_2 2
#define tick_3 3
#define tick_reset 48002
DDRC |= (1<<PC0) | (1<<PC2);
switch (intrpt){
case tick_0:
intrpt = tick_0;
PORTC &=~ (1<<PC0) | (1<<PC2);
break;
case 1:
intrpt = tick_1;
PORTC |= (1<<PC0) | (1<<PC2);
break;
case 2:
intrpt = tick_2;
PORTC &=~ (1<<PC0) | (1<<PC2);
break;
case 3:
intrpt = tick_3;
PORTC |= (1<<PC0) | (1<<PC2);
break;
case tick_reset:
intrpt = tick_reset;
(OCR0A = -1 );
break;
// init lcd
lcd_init();
FILE lcd_stream = FDEV_SETUP_STREAM(lcd_putchar, 0, _FDEV_SETUP_WRITE);
lcd_home();
// init serial port
uart_init();
FILE uart_stream = FDEV_SETUP_STREAM(uart_putchar, uart_getchar, _FDEV_SETUP_RW);
stdin = stdout = &uart_stream;
lcd_line_one();
fprintf_P(&lcd_stream,
PSTR(" Timer Lesson "));
}
}
return 0;
}
|
March 17, 2011
by bretm
|
You need the perform the switch statement and increment the variable every 5ms. It's the interrupt routine that happens every 5ms, but your interrupt routine is empty:
ISR(TIMER0_COMPA_vect){
}
Even if it wasn't empty, it wouldn't fire because the TIMSK0 register isn't set.
Have section 14.9 of your datasheet handy. Read it if you haven't already. Even better to read all of chapter 14. Then pretend you're the microcontroller and walk through the main() routine to see whats happening:
tick = 0;
// Sets tick equal to 0. Good.
OCR0A = 71;
// Sets the OCR0A register. Good.
TCCR0A = (1<<COM0A1) | (1<<WGM01)|(1<<CS10);
// Sets the WGM01 bit. Puts Timer0 into CTC mode. Good.
// Sets the CS10 bit. That doesn't exist in TCCR0A. Bad random stuff.
// Sets the COM0A1 bit. Hooks up hardware pin PD6 to the timer. Not wanted.
TCCR0B = (1<<CS00) | (1<<CS01);
// Sets the prescaler to clk/64. Bad. We need clk/1024 (CS00 and CS02).
while(1) {
// Start infinite loop. Good. I'll do the following stuff infinity times:
tick++;
// Increment state variable. Hey, that's supposed to happen every 5ms,
// not every time through the loop. Bad.
sei();
// Enable interrupts. We don't need to do that infinity times. Just once.
DDRC |= (1<<PC0) | (1<<PC2);
// Configure PC0 and PC2 for output. Infinity times. Only need to do it once.
switch(intrpt){
// Hmm. I know that intrpt is an int, but nobody told me what value it contains yet.
// Maybe static electricity set it to 777 on power-up. Or maybe it's 0.
// If it's 0, then I go here:
case tick_0:
intrpt = tick_0;
// Wait, whenever intrpt is 0, he wants me to set intrpt to 0? OK, I obey!
PORTC &=~ (1<<PC0) | (1<<PC2);
// Pins pulled low. Good.
break;
// OK, I'll skip all the other cases. Good.
lcd_init();
// Hey, what's this? I expected a "case". Does this actually compile?
// You want me to initialize the LCD again and again? OK, I obey!
Et cetera.
So you need to move the state logic into the interrupt routine.
You need to set bit OCIE0A of register TIMSK0 in order to enable the interrupt routine.
You need to increment after the switch if you want to hit case 0 the first time through.
You never need to change OCR0A after you set it to 71. You definitely don't want to set it to -1. You want to set "tick" to -1 after 48002 ticks have happened so that the whole sequence starts over.
You need to move the one-time initialization of DDRC, sei(), lcd, etc. outside of the while(1) loop. The while(1) loop will be empty when you're done.
I don't know what intrpt is for, doesn't look like it's needed. It's "tick" that you want to use in the switch(). |
March 17, 2011
by Linkster
|
Is this how you set TIMSK0, OCIE0A bit to 1 ?
TIMSK0 = (1<<OCIE0A);
This is where I get lost with the Data sheet. Untill I write the stuff I won't know what to look for. |
March 17, 2011
by bretm
|
I would use "|=" instead of "=" just like you did with DDRC and PORTC. Otherwise, yeah. The difference is that |= leaves all the other bits alone, where = causes the other bits in that register to be turned off. |
March 19, 2011
by Linkster
|
OK Bret,
I'm back. I read all of chapter 14 to the best I could understand it. I made notes next to each line of code as I went through it. I also pretended I was the microcontroller and walked through it (made sence to me). Went back to all the posts on this thread, checked , double checked and made more notes. So here is the plain truth.
Unless someone actually reaches through my LCD screen, slaps me on the side of the head and points to the problem, I don't have a clue what I'm missing. There where times I knew I had it and just to make sure, I sat and watched the LEDs for four minutes to see if they would toggle. Nothing. Program compiles nicely with no errors. But nothing happening.
Here's what I got so far. Please enlighten me 'ol wise one. or anyone for that matter.
and Thank you for your patients.
Gary
//Timer0 lesson
//for Atmega 168
#define F_CPU 14745600
#include <stdio.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <inttypes.h>
#include "../libnerdkits/delay.h"
#include "../libnerdkits/lcd.h"
#include "../libnerdkits/uart.h"
// PC0 = red LED
// PC2 = caliper sleep timer reset.
//**********GLOBAL VARIABLES********************
volatile uint16_t intrpt ;
void tick_set(uint16_t x) { // Not sure about this area.
OCR0B = x; }
//**************INTERRUPT Routine***************
ISR(TIMER0_COMPA_vect)
//perform the switch statement and increment the variable every 5ms
{
TCNT0 = 0; // Clear timer counter
OCR0A = 72; // set output compare value (72= 5ms) creates a interrupt
intrpt = 48003; // the timer counts to this before reseting to zero
intrpt++; // increment counter
}
//**************Make it Happen******************
int main()
{
TCNT0 = 0; //Clear timer counter
//***************set the timer******************
TIMSK0 |= (1<<OCIE0A); //output compare match A interrupt
TCCR0A |= (1<<WGM01); //clear timer on compare match, OCRA is top, Update is imediate, tov flag max
TCCR0B |= (1<<CS00) | (1<<CS02); //set 1024 prescale on clk-A and clk-B
sei(); //enable inturrupts
DDRC |= (1<<PC0) | (1<<PC2);
DDRB |= (1<<PB3);
PORTB |= (1<<PB3);
#define tick_0 0 //start of clock (1st intrrupt)
#define tick_1 1 // @ 5ms (2nd intrrupt)
#define tick_2 2 // @ 10ms (3rd interrupt)
#define tick_3 3 // @ 15ms (4th interrupt)
#define tick_4 48003 // @ 240.015sec (start over)
switch (intrpt){
case 0:
intrpt = tick_0;
PORTC &=~ (1<<PC0) | (1<<PC2);
//break;
case 1:
intrpt = tick_1;
PORTC |= (1<<PC0) | (1<<PC2);
//break;
case 2:
intrpt = tick_2;
PORTC &=~ (1<<PC0) | (1<<PC2);
//break;
case 3:
intrpt = tick_3;
PORTC |= (1<<PC0) | (1<<PC2);
//break;
case 4:
intrpt = tick_4;
(intrpt = -1 );
//break;
}
//******************LCD & Serial Port***************
// init lcd
lcd_init();
FILE lcd_stream = FDEV_SETUP_STREAM(lcd_putchar, 0, _FDEV_SETUP_WRITE);
lcd_home();
// init serial port
uart_init();
FILE uart_stream = FDEV_SETUP_STREAM(uart_putchar, uart_getchar, _FDEV_SETUP_RW);
stdin = stdout = &uart_stream;
lcd_line_one();
fprintf_P(&lcd_stream,
PSTR(" Timer Lesson "));
while (1){};
}
|
March 20, 2011
by Linkster
|
Made some changes. I think one of the many problems is that "intrpt" is not associated with the timer compare. Do I need to write some logic between the output compare flag OCF0A and intrpt?? I did try writing (intrpt = OCFA) and the result was a locked up MCU.
Still don't see no blinking LEDs.
Anyone?
//Timer0 lesson
//for Atmega 168
/*
The goal is to have two 5ms pulses to (low or "0") 5ms apart and repeat every 4 minutes.
For testing puposes, The pulse widths are increased to 250ms as noted in the state machine.
*/
#define F_CPU 14745600
#include <stdio.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <inttypes.h>
#include "../libnerdkits/delay.h"
#include "../libnerdkits/lcd.h"
#include "../libnerdkits/uart.h"
// PC0 = red LED
// PC2 = caliper sleep timer reset.
//**********GLOBAL VARIABLES********************
volatile uint16_t intrpt ;
//**************INTERRUPT Routine***************
ISR(TIMER0_COMPA_vect)
//perform the switch statement and increment the variable every 5ms
{
TCNT0 = 0x00; // Clear timer counter
OCR0A = 72; // set output compare value. creates an interrupt (72 clock ticks = 5ms)
OCR0B = 0x00; // set output compare value of timer_B (is this needed???)
//OCF0A; // incase I need it
intrpt = 0x00; // How do I associate this with the timer??????????
//intrpt++; // increment counter????
}
//**************Make it Happen******************
int main()
{
sei(); //enable inturrupts
//****************Timer settings****************
TIMSK0 |= (1<<OCIE0A); //output compare match A interrupt enabled
TCCR0A |= (1<<WGM01); //clear timer on compare match, OCRA is top, Update is imediate, tov flag max
TCCR0B |= (1<<CS00) | (1<<CS02); //set 1024 prescale on clk-A and clk-B
TIFR0 |= (1<<OCF0A); // Interrupt Flag Register enabled. Do I need this flag for the intrpt????
//***************Set the pins*******************
DDRC |= (1<<PC0) | (1<<PC2)| (1<<PC5);
PORTC |= (1<<PC5) ;
DDRB |= (1<<PB3);
PORTB |= (1<<PB3);
/*****************TEST LEDs*********************
PORTC |= (1<<PC5) | (1<<PC0) | (1<<PC2);
delay_ms (500);
PORTC &=~ (1<<PC5);
delay_ms (250);
PORTC |= (1<<PC5);
delay_ms (250);
PORTC &=~ (1<<PC5);
delay_ms (250);
PORTC |= (1<<PC5);
**********************************************/
//****************State Machine*****************
//increased steps and pulse duration so I can see the result sooner
#define tick_0 50 // start (1st interrupt) pins low
#define tick_1 100 // @ 250ms (2nd interrupt) pins high
#define tick_2 150 // @ 250ms (3rd interrupt) pins low
#define tick_3 200 // @ 250ms (4th interrupt) pins high
#define tick_4 48002 // @ 240.015sec (start over)
switch (intrpt){
case 0:
intrpt = tick_0;
PORTC &=~ (1<<PC0) | (1<<PC2);
//break;
case 1:
intrpt = tick_1;
PORTC |= (1<<PC0) | (1<<PC2);
//break;
case 2:
intrpt = tick_2;
PORTC &=~ (1<<PC0) | (1<<PC2);
//break;
case 3:
intrpt = tick_3;
PORTC |= (1<<PC0) | (1<<PC2);
//break;
case 4:
intrpt = tick_4;
(TCNT0 = -1);
break;
}
//******************LCD & Serial Port***************
// init lcd
lcd_init();
FILE lcd_stream = FDEV_SETUP_STREAM(lcd_putchar, 0, _FDEV_SETUP_WRITE);
lcd_home();
// init serial port
uart_init();
FILE uart_stream = FDEV_SETUP_STREAM(uart_putchar, uart_getchar, _FDEV_SETUP_RW);
stdin = stdout = &uart_stream;
lcd_line_one();
fprintf_P(&lcd_stream,
PSTR(" Timer Lesson "));
while (1);
}
|
March 20, 2011
by Noter
|
I think you need to see a working example, search the web on "avr timer tutorial". There are several to choose from. |
March 20, 2011
by Noter
|
This one is pretty straight forward ...
http://extremeelectronics.co.in/avr-tutorials/avr-timers-an-introduction/ |
March 20, 2011
by Linkster
|
Thank you Noter,
I'll read it through and see what I'm missing. I studied Mrobbins realtimeclock, That was a great example. It looks like I have the timer set up pretty close to his. By writing the interrupt (intrpt) to the LCD, I found that infact the timer was counting but hanging up on case 4:
I'll do some more head pounding and see what I can figure out.
Thanks again,
Gary |
March 20, 2011
by Noter
|
After you get a handle on the basic timer, you'll want to take a look at the compare modes next ... then you'll have it.
http://extremeelectronics.co.in/avr-tutorials/timers-in-compare-mode-part-i/
http://extremeelectronics.co.in/avr-tutorials/timers-in-compare-mode-part-ii/ |
March 21, 2011
by bretm
|
Here's how I would structure the program.
// #includes and stuff
volatile uint16_t intrpt;
// this will run once every 5ms
ISR(TIMER0_COMPA_vect)
{
switch (intrpt)
{
case 0:
case 2:
// do something here
break;
case 1:
case 3:
// do something here
break;
case 48002:
intrpt = -1;
break;
}
intrpt++;
// no need to mess with OCR0A or TCNT0 here
}
int main()
{
intrpt = 0;
// set timer registers including OCR0A. Ignore TCNT0
// initialize LCD
// enable interrupts
// this loop is not associated with the timer
while (1)
{
lcd_home();
// display stuff
// If you need to examine intrpt you have to
// disable interrupts, copy the value, and re-enable interrupts
}
}
|
March 21, 2011
by bretm
|
And there's no need to wait 4 minutes while testing. Use 1000 instead of 48000 and you only have to wait 5 seconds. Once it's working change it back to the real value. |
March 21, 2011
by bretm
|
The only place you should ever modify "intrpt" is when you set it to 0 at the start of your program, and when you increment it during the interrupt routine. It most of the code you've posted, you're setting intrpt to the same value that it is already equal to, e.g.
switch(intrpt)
{
case 3:
intrpt = tick_3;
}
which is equivalent to
if (intrpt == 3)
{
intrpt = 3;
}
In that last code you posted you had tick_3 = 200 (which is fine for testing) but you still had "case 3:" which compares intrpt to 3. Look up a C reference to see how the switch statement works, or else keep using if/else if you're more comfortable with that.
But the most important thing missing from all the code you posted is that you have to do the comparisons against intrpt during the interrupt routine, not in main(). It's the interrupt routine that runs every 5ms. Nothing in main() is regulated by the timer. |
March 21, 2011
by bretm
|
Posted too soon. The only place you should ever modify "intrpt" is when you set it to 0 at the start of your program, when you increment it during the interrupt routine, and when you reset it to -1 after you hit 48002. |
March 21, 2011
by Linkster
|
"Hallelujah"
It works. just proves you can teach a dog a new trick. The only thing that is not working is PC2. Only PC0 is firing. Also, while writing the if statements I left out tick_3 and tick_4 only because I wanted to see if it would compile to that point plus I didn't know how to write the two ticks in the same statement.
I cycled the MCU and VIOLA, it started working. I even was able to have the interrupt displayed on the LCD while executing. I can see the timer count up and reset.
I looked at Mike's realtime clock, used the functioning timer and moved a few things around like my teacher Bret told me to do. I have to give you a big thank you Bret. Rather than just write it out for me, you held my nose to the grinder. Well! you kinda wrote it out but I now have a good understanding of timers. This one anyways.
Gotta say thanks to Norter also, for the links to the web. they were great referances.
Here's the semi working code. And thanks again.
//Timer0 lesson
//for Atmega 168
/*
The goal is to have two 5ms pulses to (low or "0") 5ms apart and repeat every 4 minutes.
For testing puposes, The pulse widths are increased to 250ms as noted in the state machine.
*/
#define F_CPU 14745600
#include <stdio.h>
#include <stdlib.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <inttypes.h>
#include "../libnerdkits/delay.h"
#include "../libnerdkits/lcd.h"
#include "../libnerdkits/uart.h"
// PC0 = red LED
// PC2 = caliper sleep timer reset.
void calipersleeptimer_setup() {
// setup Timer0:
// CTC (Clear Timer on Compare Match mode)
// TOP set by OCR0A register
TCCR0A |= (1<<WGM01);// | (1<<COM0A1);
// clocked from CLK/1024
// which is 14745600/1024, or 14400 increments per second
TCCR0B |= (1<<CS02) | (1<<CS00);
// set TOP to 72
// because it counts 0, 1, 2, ... 72, 143, 0, 1, 2 ...
// so 0 through 72 equals 72 events
OCR0A = 72;
// enable interrupt on compare event
// 5ms = 72 clock cycles (14400 / 72 = 200 per second) or 48004 in 4 min 15ms.
TIMSK0 |= (1<<OCIE0A);
}
//**********GLOBAL VARIABLES********************
volatile uint16_t intrpt ;
//****************State Machine*****************
//increased steps and pulse duration so I can see the result sooner
#define tick_0 (0) // start (1st interrupt) pins low
#define tick_1 (50) // @ 250ms (2nd interrupt) pins high
#define tick_2 (100) // @ 250ms (3rd interrupt) pins low
#define tick_3 (150) // @ 250ms (4th interrupt) pins high
#define tick_4 (1000) // @ 240.015sec (start over)
ISR(TIMER0_COMPA_vect){
if (tick_0 == intrpt){
PORTC &=~ (1<<PC0) | (1<<PC2);}
if (tick_1 == intrpt){
PORTC |= (1<<PC0) | (1<<PC2);}
switch (intrpt){
case tick_0:
case tick_2:
PORTC &=~ (1<<PC0) | (1<<PC2);
break;
case tick_1:
case tick_3:
PORTC |= (1<<PC0) | (1<<PC2);
break;
case tick_4:
intrpt = -1;
break;
}
intrpt++;
}
void sIGNAL(SIG_OUTPUT_COMPARE0A) {
// when Timer0 gets to its Output Compare value,
// one one-hundredth of a second has elapsed (0.01 seconds).
intrpt++;
}
//**************Make it Happen******************
int main(){
calipersleeptimer_setup();
intrpt = 0;
sei(); //enable inturrupts
//***************Set the pins*******************
DDRC |= (1<<PC0) | (1<<PC2)| (1<<PC5);
PORTC |= (1<<PC5) ;
DDRB |= (1<<PB3);
PORTB |= (1<<PB3);
/*****************TEST LEDs*********************
PORTC |= (1<<PC5) | (1<<PC0) | (1<<PC2);
delay_ms (500);
PORTC &=~ (1<<PC5);
delay_ms (250);
PORTC |= (1<<PC5);
delay_ms (250);
PORTC &=~ (1<<PC5);
delay_ms (250);
PORTC |= (1<<PC5);
**********************************************/
//******************LCD & Serial Port***************
// init lcd
lcd_init();
FILE lcd_stream = FDEV_SETUP_STREAM(lcd_putchar, 0, _FDEV_SETUP_WRITE);
lcd_home();
// init serial port
uart_init();
FILE uart_stream = FDEV_SETUP_STREAM(uart_putchar, uart_getchar, _FDEV_SETUP_RW);
stdin = stdout = &uart_stream;
lcd_line_one();
fprintf_P(&lcd_stream,
PSTR(" Timer Lessons "));
while(1) {
lcd_line_two();
fprintf_P(&lcd_stream, PSTR("%16.2f ms"), (double) intrpt);
}
}
|
March 21, 2011
by Linkster
|
left out of the previous post.
AN Old Old Dog. |
March 22, 2011
by bretm
|
I don't see any problems with PC2 in the code. I would check the wiring.
OCR0A should be 71, not 72. The timer will count 0 to 71 and start over. That's 72 values. If you set it to 72 it will count from 0 to 72 which is 73 numbers, so the interrupt will fire every 5.06944444ms. The //comments you have above this are mistaken.
In the ISR you don't need the "if" statements. The "switch" statement does what those two if statements are doing.
You can remove the "sIGNAL" routine. It won't run because the spelling doesn't match what the compiler would look for to treat it as an interrupt, and if it did run you'd be incrementing intrpt too many times.
I would set DDRC before enabling interrupts. It's no big deal, though. The way its written I don't think the interrupt will fire before DDRC gets set.
The fprintf_P seems to display the value of intrpt followed by "ms". If you want to show milliseconds elapsed you would have to multiply intrpt by 5 first:
cli();
uint16_t ms = intrpt * 5;
sei();
fprintf_P(&lcd_stream, PSTR("%16.2f ms"), (double) ms);
Technically, in your main while loop, when you read "intrpt" you should disable interrupts, copy its value, and then re-enable the interrupts. What can happen on rare occasion is this:
- The fprintf_P statement starts to read the two-byte intrpt value. It reads the first byte, and then
- The interrupt fires. The value of intrpt changes.
- fprintf_P continues to run and reads the second byte of intrpt, which may no longer correspond to the first byte.
Example:
Let's say intrpt is equal to 255. This is represented by the two bytes "0" and "255". I don't know which one fprintf_P reads first, but let's say it reads the "255" first. It may even read it multiple times.
Now the interrupt happens and intrpt is incremented to "256". Instead of "0" and "255" it's now represented by the bytes "1" and "0".
Now fprintf_P reads the other byte of "intrpt" and it gets a "1". So it sees bytes "1" and "255", which represents the value 511. So it would print 511 even though it was 255 and changed to 256.
You might not notice in your example because you immediately start the loop again and overwrite the previous value with a new copy, which will always correct it because the loop is faster than 5ms.
But in some applications it matters. |
March 22, 2011
by bretm
|
Glad you got it working! |
March 22, 2011
by Linkster
|
Bret,
I had cleaned up the program this morning. I did find out that the "if" statements were not needed along with the signal part. Once I had things going last night, I just need to tell you. I was pretty happy about it.
I wired up the caliper today and tested the timer. The caliper now stays on without going into sleep mode and the caliper display has only a small flicker during reseting of the caliper timer.
Now I will impliment this timer into the caliper data/clock DRO program and see what happens. I'm sure I'll have to pay special attention to placement of the timer code.
I here by submit my final caliper_timer code for your grading.
Thanks again Teacher,
Gary
//caliper_timer
//for Atmega 168
//Linkster
/*
Two 5ms pulses to (low or "0") 5ms apart and repeat every 4 minutes.
For testing puposes, Decrease #define tick_5 to (1000) for 5 sec cycles.
*/
#define F_CPU 14745600
#include <stdio.h>
#include <stdlib.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <inttypes.h>
#include "../libnerdkits/delay.h"
#include "../libnerdkits/lcd.h"
#include "../libnerdkits/uart.h"
// PC0 = Caliper reset.
// PC3 = Caliper power.
void caliper_timer() {
// setup Timer0:
// CTC (Clear Timer on Compare Match mode)
// TOP set by OCR0A register
TCCR0A |= (1<<WGM01);// | (1<<COM0A1);
// clocked from CLK/1024
// which is 14745600/1024, or 14400 increments per second
TCCR0B |= (1<<CS02) | (1<<CS00);
// set TOP to 71
// because it counts 0, 1, 2, ... 71, 0, 1, 2,....
// so 0 through 71 equals 72 events
OCR0A = 71;
// enable interrupt on compare event
// 5ms = 72 clock events (14400 / 72 = 200 per second) or 48003 in 4 min 15ms.
TIMSK0 |= (1<<OCIE0A);
}
//****************GLOBAL VARIABLES**************
volatile uint16_t intrpt ;
//****************State Machine*****************
// PC0 signal is inverted though a 2n3904.
// Emitter to ground. Base tied to PC0.
// The collector is tied to the on/off button of the caliper.
#define tick_0 (5) // caliper turned on
#define tick_1 (10) // @ 10ms (1st interrupt) pin low
#define tick_2 (15) // @ 15ms (2nd interrupt) pin high
#define tick_3 (20) // @ 20ms (3rd interrupt) pin low
#define tick_4 (25) // @ 25ms (4th interrupt) pin high
#define tick_5 (43002) // @ 240.015sec (start over)
ISR(TIMER0_COMPA_vect){
switch (intrpt){
case tick_0:
PORTC &=~ (1<<PC0);
break;
case tick_1:
case tick_3:
PORTC |= (1<<PC0);
break;
case tick_2:
case tick_4:
PORTC &=~ (1<<PC0);
break;
case tick_5:
intrpt = 0;
break;
}
intrpt++;
}
//***************Make it Happen*****************
int main(){
caliper_timer();
sei(); //enable inturrupts
//***************Set the pins*******************
DDRB |= (1<<PB3);
PORTB |= (1<<PB3);
DDRC |= (1<<PC0) | (1<<PC3);
PORTC |= (1<<PC3);
//***************LCD & Serial Port**************
// init lcd
lcd_init();
FILE lcd_stream = FDEV_SETUP_STREAM(lcd_putchar, 0, _FDEV_SETUP_WRITE);
lcd_home();
// init serial port
uart_init();
FILE uart_stream = FDEV_SETUP_STREAM(uart_putchar, uart_getchar, _FDEV_SETUP_RW);
stdin = stdout = &uart_stream;
lcd_line_one();
fprintf_P(&lcd_stream,
PSTR(" Caliper Timer "));
// loop forever
while(1) {
lcd_line_two();
fprintf_P(&lcd_stream, PSTR("%16.2f sec"), (double) intrpt/200);
}
}
|
March 22, 2011
by bretm
|
Good. If it works, it works.
But your switch statement was better before. The caliper pin is staying low (caliper on) for almost 4 minutes now, instead of high like before. The total period is no longer 4min + 15ms, it's more like 3:35. Maybe that's intentional. Doesn't hurt anything in this application.
But the comments in this latest incarnation, along with the specific cases you implemented, suggest there's still a bit of a misunderstanding about what's going on.
You have an interrupt routine that fires every 5ms. The first time it fires, intrpt is 0 because that's what you initialized it to (actually you didn't in this version but you did before). The second time it fires, intrpt is 1 because you incremented it the first time the interrupt routine ran. The third time it fires, intrpt is 2, etc.
So let's follow the logic:
switch (intrpt){
// we're going to jump to the case that matches the intrpt value
case tick_0:
// tick_0 is 5, so this will happen the 6th time we enter this routine
// which means we run this 30ms after starting
PORTC &=~ (1<<PC0);
// turns on the caliper at 30ms time
break;
// skip the other cases
case tick_1:
// tick_1 is 10, so this happens the 11th run, which is 55ms in
case tick_3:
// and this happens on the 21st interrupt, after 105ms have passed
PORTC |= (1<<PC0); // turn off the caliper
break; // and skip the other cases
case tick_2:
// tick_2 is 15, so this case occurs after 80ms
case tick_4:
// tick_4 is 25, so this case occurs after 130ms
PORTC &=~ (1<<PC0); // turn on the caliper
break; // and skip the other cases
case tick_5:
// after 3 mins, 35 sec, 15 ms
intrpt = 0; // set intrpt to 1 (we're about to increment it so it won't be 0)
break;
}
intrpt++; // increment
So the timeline this describes is:
30ms turn on
55ms turn off
80ms turn on
105ms turn off
130ms turn on
...
3min 35s 15ms start over at 5ms into timeline (not at 0)
25ms turn on (but was already on)
50ms turn off
etc.
|
March 22, 2011
by Linkster
|
Hi Bret,
OK, I guess I got messed up looking at tick_x number and started thinking it was ms rather than interrupts. I do realize know that 1 interrupt = 5ms. I made the corrections. I also fixed the discription on the top to read two 25ms pulses 25ms apart.
I found during testing that the caliper did need 30ms from the power on time to recognize the first pulse. Tick_0 was required to turn the calipers display on. I also tried smaller pulses on the interrupts but lost signal below 20ms. I'm still confused on setting tick_5 to -1 . zero seamed to work also without anything noticable. I put it back to -1 .
I am now trying to place the timer code into the caliper_dro code. There seems to be timing issues and small delays required by the caliper code that effects the proper operation of the timer. I need to go back and read your previous posts. I beleive you had mentioned where to place code in this program.
I was thinking that I probably could of just monitored one of the clock or data pins from within the MCU and when the signal ceased, fire PC0 once to reactivate the caliper. The caliper never loses it's position during sleep but does stop transmitting.
I'm still listening and learning,
Gary
|
March 22, 2011
by Linkster
|
Update:
The timer program and caliper dro are now One. Everything is working perfect. Just need to polish up a couple things and then I'll post the code. |
March 23, 2011
by bretm
|
Woohoo! |
March 23, 2011
by Linkster
|
Hope this works.
Thanks Bret,
Gary |
March 23, 2011
by Linkster
|
Like everything else, I have to do it multiple times before it's right.
Thanks Teach,
Gary |
March 24, 2011
by Linkster
|
I wanted to move PC0 to PB4 but found it did not transmit any pulses. I made all the required changes to the code. Is there something different about PB3 that is preset in the bootloader that makes it different from PC0?
Here are my current pin setups.
//***************Set the pins*******************
// SET DDRB PINS 1 2 AS INPUT
DDRB &=~ (1<<PB1)|(1<<PB2);
// Set DDRB pin 3 as output
DDRB |=(1<<PB3);
// PULL UP PINS 1 2
PORTB |=(1<<PB1)|(1<<PB2);
// set PC0 as output
DDRC |=(1<<PC0);
And this is what I want.
//***************Set the pins*******************
// SET DDRB PINS 1 2 AS INPUT
DDRB &=~ (1<<PB1)|(1<<PB2);
// Set DDRB pin 3 as output
DDRB |=(1<<PB3);
// PULL UP PINS 1 2
PORTB |=(1<<PB1)|(1<<PB2);
// set PB4 as output
DDRB |=(1<<PB4);
I don't want to set the pull up resistor for PB4. I guess you could say it needs to float.
Thanks,
Gary |
March 24, 2011
by Linkster
|
Spoke to soon. I think I found the problem. While changing everything from PC0 to PB4 , I missed changing the PORTC's to PORTB in the state machine. I'll try again.
It always seems I find the problem right after I post to the forum.
Sorry,
Gary. |
March 24, 2011
by Linkster
|
All is well. That's what it was.
Sorry |