Problems with timer (ATMEGA164A )

Go To Last Post
16 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi everyone,

I'm using the timers of an ATMGEA164A in order to read the value of an analog sensor, periodically. I state that I am using the timers for their study and for their understanding. For this study, I made a small board with veroboard and I added a 16Mhz quartz to the MCU with two 22 picofarad ceramic capacitors. Initially, I used a divider of 256, but already after a few minutes, I got a sensitive drift and time counting error. So I thought of reducing the divider to 8, thinking about improving accuracy. There has been a noticeable improvement for a while , but over time, the error became very sensitive. Below is the time when the my_delay function has finished waiting for 5 minutes:

0:41:01

0:56:01

1:01:01

1:06:01

1:11:00

1:16:00

1:21:00

2:36:00

2:41:00

8:40:10

8:45:05

8:50:09

10:00:03

10:20:48

10:34:45

 

The readings missing it is because I was not at the computer to see the instant of the writing of the sensor value transmitted via serial to the PC. Where is that wrong? This error, besides not being acceptable, worries me, because I wonder, then, how it will be possible to generate precise PWM signals.

Below is the code I wrote:

/*
 * DIAMeteo.c
 *
 * Created: 25/08/2019 19:56:17
 *
 */ 

#ifndef F_CPU
#define F_CPU 16000000L
#endif

#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include <string.h>
#include <stdlib.h>

#define BAUD 115200
#define BRC ((F_CPU/8/BAUD) -1 ) // Divido per 8 perché inizializzo USART con UCSR0A = (1 << U2X0)
#define TX_BUFFER_SIZE  128

char serialBuffer[TX_BUFFER_SIZE];
uint8_t serialReadPos = 0;
uint8_t serialWritePos = 0;
double result = 0;
char rBuf[12];
char aBuf[7];
char cBuf[6];
volatile int counter = 0;

volatile int a = 0;
volatile int cCounter = 0;
void appendSerial(char c);
void serialWrite(char  c[]);
volatile unsigned long input_delay;

void ADC_init(void){
	ADMUX |= (1 << REFS0) | (1 << MUX0);
	ADCSRA |= (1 << ADEN) | (1 << ADIF)  | (1 << ADIE) | (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0);
	ADCSRA |= (1 << ADATE);
	ADCSRA |= (1 << ADSC);
}

void USART_init(void){
	/* Inzio l'inizializzazione della USART rifattorizzare con una libreria (.h)
	 * UBRRn registro per impostare il baud rate
	*/
	UBRR0H = (BRC >> 8);
	UBRR0L = BRC;
	UCSR0A = (1 << U2X0);
	UCSR0B = (1 << TXEN0)	| (1 << TXCIE0); /* enable serial transmission and enable interrupt generation when transmission is complete*/
	UCSR0C = (1 << UCSZ01)	| (1 << UCSZ00); /* Set 8bit */
}

void my_delay(unsigned long number_of_16_384ms_interrupts);
void init_timer0_ovf_interrupt(void); 

int main(void)
{
	DDRD |= (1 << PD7);

	USART_init();
	ADC_init();
	init_timer0_ovf_interrupt();
	sei();
	PORTD = (1 << PD7); //Accendo led verde - stato device ON

    while (1)
    {
		result = (11500/47.0*(49.6*a/1024.0)/10.0) + 24.555 + 22.50; //24.555 delta for calibration sensor //+22,50 for about 180m AMSL
		dtostre(result, rBuf, 3, DTOSTR_PLUS_SIGN);
		serialWrite(rBuf);
		serialWrite("\n\r");
		unsigned long d = 2343751;
		my_delay(d); /* equivalent 30 seconds) */
    }

	return 0;
}

void appendSerial(char c)
{
	serialBuffer[serialWritePos] = c;
	serialWritePos++;

	if(serialWritePos >= TX_BUFFER_SIZE)
	{
		serialWritePos = 0;
	}
}

void serialWrite(char c[])
{
	for(uint8_t i = 0; i < strlen(c); i++)
	{
		appendSerial(c[i]);
	}

	if(UCSR0A & (1 << UDRE0))
	{
		UDR0 = 0;
	}
}

ISR(USART0_TX_vect)
{
	if(serialReadPos != serialWritePos)
	{
		UDR0 = serialBuffer[serialReadPos];
		serialReadPos++;

		if(serialReadPos >= TX_BUFFER_SIZE)
		{
			serialReadPos = 0;
		}
	}
}

ISR(ADC_vect){
	a = ADC;
	counter++;  //TODO to detelte
	cCounter = counter; //TODO to delete
}

void my_delay(unsigned long number_of_0_128ms_interrupts){
	TCNT0 = 0x00; // reset timer0 contatore interno del timer
	input_delay = 0; // resetto il contatore di quanti overflow sono già scattati (variabile che viene confrontata con il parametro del metotodo come si vede nel ciclo while

	while(input_delay <= number_of_0_128ms_interrupts){
		PORTD ^= (1 << PD7);
	}		

}

ISR(TIMER0_OVF_vect){
	input_delay++;
}

void init_timer0_ovf_interrupt(void){
	/* set divider (8) */
	TCCR0B = 0x02;
	TIMSK0 = 0X01;
}

Below the image the quartz 16Mhz (EA1570 - 16.000 / 1604C). It's a quartz, not a ceramic oscillator, right?

QUARTZ 16Mhza

Thank you so much in advance for your usual kindness and patience

This topic has a solution.
Last Edited: Wed. Sep 4, 2019 - 10:53 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Do not set the timer while it is running.
It causes an interrupt at an unexpected timing and gives incorrect results.

Set, operate and stop the timer in my_delay.

 

one more.
If you want to evaluate input_delay, get a copy by atomic processing.
Since this value has 4 bytes, it will be an invalid value if an interrupt occurs during transfer.

Last Edited: Tue. Aug 27, 2019 - 10:26 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I have suggested you look at the Arduino core code for examples once before. See how it does millis().
I would not worry about the accuracy of your hardware just yet.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

kabasan wrote:

Do not set the timer while it is running.
It causes an interrupt at an unexpected timing and gives incorrect results.

Set, operate and stop the timer in my_delay.

I'd say to make a "soft timer" based on a convenient "tick" value, and never start/stop the timer.  With a bit of care any interrupt latency can be handled.

 

I usually use 10ms ticks.  Apparently you are using 128us?  Doable, but you must be careful.

 

So you are very close to that approach.  I can't see why there should be missing reported values.  Aren't you sending to a terminal emulator or similar?  What happened when you were away?   IMO/IME there is no need to sit in one place when doing soft timers; whenever a tick occurs you make one pass through the table of soft timers, update each, and set the "alarm went off" flag as appropriate.

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

kabasan wrote:

Do not set the timer while it is running.
It causes an interrupt at an unexpected timing and gives incorrect results.

Set, operate and stop the timer in my_delay.

 

one more.
If you want to evaluate input_delay, get a copy by atomic processing.
Since this value has 4 bytes, it will be an invalid value if an interrupt occurs during transfer.

 

Hello,

Thank you for your intervention. However, I'm not sure I understood everything. You said, "Do not set the timer while it is running.".

However, what do you regret when you say do not set the time while it is running? Are you referring to the resetting of the counter associated to the timer, at the input of the my_delay function (TCNT0 = 0x00;)?

Because I actually start in counter in the initialization method:

void init_timer0_ovf_interrupt (void) { / * set divider (8) * / TCCR0B = 0x02; TIMSK0 = 0X01; }

Perhaps you want to tell me to bring the assignment of TIMSK0 = 0X01 into the my_delay method; instead of void init_timer0_ovf_interrupt (void) and ending the while set TIMSK0 = 0X00?

Also, you suggested: "If you want to evaluate input_delay, get a copy by atomic processing." If the concept is clear to me (reasons for atomicity), how and where to achieve it?

I imagine that for the copy you refer to the variable input_delay.

I thank you very much. Your help and the help of all the members of this beautiful forum are truly precious.

Thank you again for your kindness and patience.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

theusch wrote:

 

I'd say to make a "soft timer" based on a convenient "tick" value, and never start/stop the timer.  With a bit of care any interrupt latency can be handled.

 

I usually use 10ms ticks.  Apparently you are using 128us?  Doable, but you must be careful.

 

So you are very close to that approach.  I can't see why there should be missing reported values.  Aren't you sending to a terminal emulator or similar?  What happened when you were away?   IMO/IME there is no need to sit in one place when doing soft timers; whenever a tick occurs you make one pass through the table of soft timers, update each, and set the "alarm went off" flag as appropriate.

 

Hi theusch,

I apologize, undoubtedly I expressed myself badly.

I wanted to say that since I didn't have an internal clock at the ATMEGA I couldn't set the times. So the time I transcribed it by hand when I was in front of the computer, in doing so, I highlighted the temporal drift. Sorry again.

From what I seem to understand from what you write is that what I wrote is not entirely wrong. I'm starting now my learning path about MCU firmware development. What do you mean by "soft timer" and what am I doing wrong?

What do you mean by "soft timer" and what am I doing wrong I also don't think I have understood Kabasan's advice well. Starting and stopping the timer does not itself involve a drift concerning absolute time?

I am very grateful and even more so if you open yourself a little better than you are trying to make me understand.

I am grateful to you. I must say that this avrfreaks is a place full of wonderful and helpful people.

Thank you so much again.

Excuse me if I don't have a good capacity for expression in your native language.

Bye

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

If the CPU speed is 16MHz set a 16 bit timer to use a /1024 prescaler then set a compare register to 15624 running the timer in CTC mode and you will get a compare interrupt exactly once a second. That is then your timebase for all other timing activities. Of course you might prefer to use a 100ms or even 10ms timebase?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

It should be understood that doing these while the timer is running can cause bugs. This is not a problem this time, but this way of writing will make you unhappy in the future.

TCNT0 = 0x00;
input_delay = 0;

Second, input_delay is a 4-byte variable that takes 4 instructions and 8 cpu clocks to load from memory.

Let's assume that input_delay is currently 0x000000FF.
Start loading from memory to evaluate input_delay with my_delay function.
First get 0xFF of the least significant byte.
Unfortunately, an interrupt occurs here and input_delay is updated to 0x00000100.
The interrupt is complete and the my_delay function loads the second byte, which is 0x01.

As a result of loading up to the 4th byte, the value obtained by the my_delay function is 0x000001FF.

 

The input_delay is 0x00000100, but the program lies that it is 0x000001FF.I think this is the cause of the problem.

To prevent this, evaluate or copy while interrupts are disabled.

 

unsigned long input_delay_copy;

cli();
input_delay_copy = input_delay;
sei();
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

kabasan wrote:

It should be understood that doing these while the timer is running can cause bugs. This is not a problem this time, but this way of writing will make you unhappy in the future.

TCNT0 = 0x00;
input_delay = 0;

Second, input_delay is a 4-byte variable that takes 4 instructions and 8 cpu clocks to load from memory.

Let's assume that input_delay is currently 0x000000FF.
Start loading from memory to evaluate input_delay with my_delay function.
First get 0xFF of the least significant byte.
Unfortunately, an interrupt occurs here and input_delay is updated to 0x00000100.
The interrupt is complete and the my_delay function loads the second byte, which is 0x01.

As a result of loading up to the 4th byte, the value obtained by the my_delay function is 0x000001FF.

 

The input_delay is 0x00000100, but the program lies that it is 0x000001FF.I think this is the cause of the problem.

To prevent this, evaluate or copy while interrupts are disabled.

 

unsigned long input_delay_copy;

cli();
input_delay_copy = input_delay;
sei();

 

Hi kabasan,
You have well explained. Excellent explanation and in fact, what you observe seems a very important issue, which I do not believe can be ignored in a concurrent context.
I still have a couple of doubts:
1. TCNT0 does not reset it start of my_delay? In other words, if I omit TCNT0 = 0x00 at each subsequent invocation will it never be reset?
2. The interruption of the cli () interrupts, the copy of the variable input_delay and the reactivation of the interrupt where I perform them inside the ISR function?
3. Does interruption of the interrupt not involve even if by little a drift of time concerning the real-time (time of our ordinary clocks)?

 

Thank you very much.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

1.
Setting TCNT0 = 0x00 is not a problem.
I'm telling you to stop the timer if you want to set it.

    TCCR0B = 0x00;      // timer stop
    TCNT0 = 0x00;
    input_delay = 0;
    TCCR0B = 0x02;      // timer run

 

2.
The cli disables interrupts, but it is simply suspended. And executed by sei.
 

3.

Even if you hold the interrupt, it is only a short time. And it does not affect the next interrupt timing.
In the first place, your while loop is not every 30 seconds.
Processing such as data conversion and serial writing also takes time.

By the way, why are you setting the delay time to 30.000128sec?

    unsigned long d = 2343751;

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi kabasan,
You are not only really competent and very good at explaining problems and filling the gaps of those who ask questions, but you are also very quick. You caught an old value 2343751. The value that was a previous attempt to reduce the time drift. The correct value is certainly 2343750. Congratulations for your attention that you dedicate to the questions that are asked. My thanks and honors to you.
I anticipate that I made the first corrections according to your first advice, and I have noticed that now everything works well. I'm curious to see tonight (in Italy) if the effects of drift still don't manifest themselves. Also tonight I will check my firmware if it is perfectly aligned with your latest clarification.
Allow me to thank you once again.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi kabasan,

Unfortunately, I continue to have the wrong behavior. I don't know if I'm wrong, I don't know if the circuit is not well done and brings interference and / or errors or even if the crystal is not resonating at the expected frequency. However, the behavior seems completely random. Initially, it seems to work well, but after a while, the system begins to have a non-deterministic time drift.

Summarize what my goal is. I want my sensor to be read at a distance of 5 minutes but at the predefined time of day, for example, 09:05, 09:10, 09:15, etc.

Not having a real clock internally, it would be enough for the sequence to be respected with respect to the time when the circuit was started.

Therefore my function my_delay, in effect, is not a real delay function but rather a function in which it expects that time intervals have elapsed since the system was started, which is why I removed the reset of the internal counter of the timer (the time continues to flow).

I don't understand where the anomaly is: software, hardware (crystal and two capacitors 20pF - not suitable or malfunctioning?).

Below I report the latest changes and the fundamental code portions.

volatile unsigned long input_delay;
unsigned long input_delay_copy;
unsigned long d = 2343750;
int main(void)
{
	DDRD |= (1 << PD7); //Imposto la PORTA PD7 (pin 21) in uscita per accendere il led indicatore di accensione scheda

	USART_init();
	ADC_init();
	init_timer0_ovf_interrupt();
	sei();

	PORTD = (1 << PD7); //Accendo led verde - stato device ON
    /* Replace with your application code */
    while (1)
    {
		result = (11500/47.0*(49.6*a/1024.0)/10.0) + 24.555 + 22.50; //29.555 delta for calibration sensor //+22,50 for about 180m AMSL
		dtostre(result, rBuf, 3, DTOSTR_PLUS_SIGN);
		serialWrite(rBuf);
		serialWrite("\n\r");
		my_delay(d); /* Equivalente a un'attesa di 30 SECONDI) */
    }

	return 0;
}

 

void init_timer0_ovf_interrupt(void){
	/* set divider 8 */
	TCNT0 = 0x00; // reset timer0 = 0
	TCCR0B = 0x02; //divder 8
	TIMSK0 = 0X01; // active the overflow interrupt
}

 

ISR(TIMER0_OVF_vect){
	input_delay++;
	cli();
	input_delay_copy = input_delay;
	sei();
}

 

void my_delay(unsigned long number_of_0_128ms_interrupts){

	input_delay = 0;
	input_delay_copy = 0;

	while(input_delay_copy < number_of_0_128ms_interrupts){
		PORTD ^= (1 << PD7);
	}		

}

 

As is evident, except for my logical errors, suspending interrupts as advised, but not resetting the counter should ensure that the timer continues to count. The interruption of interrupts does not block the increment of the count. I also doubt the indication given by in fact: TCCR0B = 0x00; and TCCR0B = 0x02; do not stop the timers but change the prescalers, correct? I don't want the timers to stop, because regardless of what happens in the microcontroller, time keeps flowing. Furthermore, I don't understand why the temporal drift always tends to anticipate the reading moment of my sensor (anticipate the exit from my while loop in the function my_delay). I do not understand where I am wrong or what may not work. I'm desperate :-) do you have any other suggestions?

Is there a technique for evaluating quartz frequency with an oscilloscope?

Thank you very much.

 

Under the behavior of the readings:

29.08.2019 00:43:02 (ower on)
29.08.2019 00:48:01 1015mb
29.08.2019 00:53:02 1015mb
29.08.2019 00:58:02 1015mb
.............
29.08.2019 01:28:02 1015mb
.....
29.08.2019 02:02:49 1014mb

 

Last Edited: Thu. Aug 29, 2019 - 08:34 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Basically, cli & sei is not used in interrupt processing.
Using sei within an interrupt will result in a special state. Please study at another opportunity.

ISR(TIMER0_OVF_vect){
    input_delay++;
}

 

The copy timing is incorrect.
Copying is done within normal processing.

void my_delay(unsigned long number_of_0_128ms_interrupts){
    input_delay = 0;
    while(1){
        cli();
        input_delay_copy = input_delay;
        sei();
        if (input_delay_copy >= number_of_0_128ms_interrupts){
            break;
        }
        PORTD ^= (1 << PD7);
    }
}

 

This reply has been marked as the solution. 
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

kabasan wrote:

Basically, cli & sei is not used in interrupt processing.
Using sei within an interrupt will result in a special state. Please study at another opportunity.

ISR(TIMER0_OVF_vect){
    input_delay++;
}

 

The copy timing is incorrect.
Copying is done within normal processing.

void my_delay(unsigned long number_of_0_128ms_interrupts){
    input_delay = 0;
    while(1){
        cli();
        input_delay_copy = input_delay;
        sei();
        if (input_delay_copy >= number_of_0_128ms_interrupts){
            break;
        }
        PORTD ^= (1 << PD7);
    }
}

 

 

Thank you so much this evening I will try your sueggestion. I let you know back.

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi, kabasan,
sorry for the delay with which I answer you.
In fact, after your correction indications, I have not had any irregular shifts in temporal drift. It indeed remains a drift that at the moment seems excessive to me. At the moment, I have a drift of about -0.4 seconds per hour.
However, compared to the reasons why I opened this discussion, the thread can be considered closed and solved.
I take this opportunity to thank you and thank all those who have intervened, bringing their help and considerations.
Compared to the timers, I have new issues that I would like to investigate with your help, but for this, I will open a new discussion.
Thanks a lot.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi @clawson,

thank you. But I have still drift, about -0.4 seconds per hour...

I will write a new discussion for other question about the timer.

Thank you so much!