Bug funzione delay?

Salve a tutti,

Questo semplice codice che attiva il Timer1 in modalità CTC non funziona col delay da 100ms iniziale, il timer non si ferma mai.
Eliminando il delay iniziale tutto funziona correttamente. C'è qualche interferenza col timer1 e la funzione delay? La cosa strana è che i successivi delay dopo la configurazione funzionano normalmente e non interferiscono.

ISR(TIMER1_COMPA_vect)        
{
  digitalWrite(2, HIGH);
  digitalWrite(2, LOW);  
}
void setup()
{
  delay(100);  
  pinMode(2, OUTPUT);    

  cli();    
  TCCR1A = 0;    
  TCCR1B = (1 << WGM12) | (1 << CS10);
  TCNT1 = 0;  
  OCR1A = 100;
  TIMSK1 = (1 << OCIE1A) | (1 << TOIE1);  
  sei();  
  
  delay(1000);
  
  cli();
  TCCR1B = 0;  // Ferma Timer, INVECE non si ferma col delay da 100 iniziale
  sei();
}
void loop(){}

x iscrizione.

Provato ed in effetti mi sembra un comportamento anomalo quantomeno, forse un side effect boo, comunque confermo IDE 0155 avr-gcc 4.8.1.

Ciao.

Sembra che il problema sia della funzione Sleep o di GCC, in ultima ipotesi del micro. Seguiranno investigazioni.

Veramente incredibile!

Arduino prima di chiamare setup() chiama una funzione interna init() che configura i timer in questo modo:

  • timer0, phase-correct PWM, prescaler 64, overflow interrupt
  • timer1, phase-correct PWM 8 bit, prescaler 64
  • timer2, phase-correct PWM 8 bit, prescaler 64

con l'istruzione

TIMSK1 = (1 << OCIE1A) | (1 << TOIE1);

nel codice si abilita overflow interrupt sul timer 1 assieme all'output Compare Match A interrupt.
L'interrupt di overflow si scatena solamente se c'è lo sleep iniziale, da qui il comportamento errato.

La soluzione è

TIMSK1 = (1 << OCIE1A);

In poche parole togliere il clock al timer, ovvero

TCCR1B = 0;

non basta da solo sempre a fermare il timer :fearful:

C'è ancora da investigare secondo me per capire bene il tutto..

Non ho verificato sto in fiducia.
Per TIMER0 sapevo per certo che lo usa e quindi lo configura, un altro timer gli serve per il PWM e il terzo (credo TIMER1 16 bit) sapevo non gli servisse.

Io penso che prima di fare cose non previste da arduino si dovrebbe sempre indagare su cosa fa e come, questo se vogliamo rappresenta il rovescio della medaglia di Arduino o in genere di qualunque collezione di funzioni preconfezionate.

Ho avuto anche qualche problema a fermare un timer in C (senza arduino), si tratta di un display 4x7 segments che ha due funzioni power_off e power_on e in effetti non si voleva fermare togliendo la sorgente del clock ora non ricordo diciamo che ho pasticciato un po e poi ha funzionato ma non ho indagato.

Ciao.

Se vero bisogna aprire una issue su GitHub (GitHub - arduino/Arduino: Arduino IDE 1.x).
Lesto sei tu il nostro reference user. :grin:

p.s.
il codice della init() è in wiring.c

void init()
{
	// this needs to be called before setup() or some functions won't
	// work there
	sei();

	// on the ATmega168, timer 0 is also used for fast hardware pwm
	// (using phase-correct PWM would mean that timer 0 overflowed half as often
	// resulting in different millis() behavior on the ATmega8 and ATmega168)
#if defined(TCCR0A) && defined(WGM01)
	sbi(TCCR0A, WGM01);
	sbi(TCCR0A, WGM00);
#endif  

	// set timer 0 prescale factor to 64
#if defined(__AVR_ATmega128__)
	// CPU specific: different values for the ATmega128
	sbi(TCCR0, CS02);
#elif defined(TCCR0) && defined(CS01) && defined(CS00)
	// this combination is for the standard atmega8
	sbi(TCCR0, CS01);
	sbi(TCCR0, CS00);
#elif defined(TCCR0B) && defined(CS01) && defined(CS00)
	// this combination is for the standard 168/328/1280/2560
	sbi(TCCR0B, CS01);
	sbi(TCCR0B, CS00);
#elif defined(TCCR0A) && defined(CS01) && defined(CS00)
	// this combination is for the __AVR_ATmega645__ series
	sbi(TCCR0A, CS01);
	sbi(TCCR0A, CS00);
#else
	#error Timer 0 prescale factor 64 not set correctly
#endif

	// enable timer 0 overflow interrupt
#if defined(TIMSK) && defined(TOIE0)
	sbi(TIMSK, TOIE0);
#elif defined(TIMSK0) && defined(TOIE0)
	sbi(TIMSK0, TOIE0);
#else
	#error	Timer 0 overflow interrupt not set correctly
#endif

	// timers 1 and 2 are used for phase-correct hardware pwm
	// this is better for motors as it ensures an even waveform
	// note, however, that fast pwm mode can achieve a frequency of up
	// 8 MHz (with a 16 MHz clock) at 50% duty cycle

#if defined(TCCR1B) && defined(CS11) && defined(CS10)
	TCCR1B = 0;

	// set timer 1 prescale factor to 64
	sbi(TCCR1B, CS11);
#if F_CPU >= 8000000L
	sbi(TCCR1B, CS10);
#endif
#elif defined(TCCR1) && defined(CS11) && defined(CS10)
	sbi(TCCR1, CS11);
#if F_CPU >= 8000000L
	sbi(TCCR1, CS10);
#endif
#endif
	// put timer 1 in 8-bit phase correct pwm mode
#if defined(TCCR1A) && defined(WGM10)
	sbi(TCCR1A, WGM10);
#elif defined(TCCR1)
	#warning this needs to be finished
#endif

	// set timer 2 prescale factor to 64
#if defined(TCCR2) && defined(CS22)
	sbi(TCCR2, CS22);
#elif defined(TCCR2B) && defined(CS22)
	sbi(TCCR2B, CS22);
#else
	#warning Timer 2 not finished (may not be present on this CPU)
#endif

	// configure timer 2 for phase correct pwm (8-bit)
#if defined(TCCR2) && defined(WGM20)
	sbi(TCCR2, WGM20);
#elif defined(TCCR2A) && defined(WGM20)
	sbi(TCCR2A, WGM20);
#else
	#warning Timer 2 not finished (may not be present on this CPU)
#endif

#if defined(TCCR3B) && defined(CS31) && defined(WGM30)
	sbi(TCCR3B, CS31);		// set timer 3 prescale factor to 64
	sbi(TCCR3B, CS30);
	sbi(TCCR3A, WGM30);		// put timer 3 in 8-bit phase correct pwm mode
#endif

#if defined(TCCR4A) && defined(TCCR4B) && defined(TCCR4D) /* beginning of timer4 block for 32U4 and similar */
	sbi(TCCR4B, CS42);		// set timer4 prescale factor to 64
	sbi(TCCR4B, CS41);
	sbi(TCCR4B, CS40);
	sbi(TCCR4D, WGM40);		// put timer 4 in phase- and frequency-correct PWM mode	
	sbi(TCCR4A, PWM4A);		// enable PWM mode for comparator OCR4A
	sbi(TCCR4C, PWM4D);		// enable PWM mode for comparator OCR4D
#else /* beginning of timer4 block for ATMEGA1280 and ATMEGA2560 */
#if defined(TCCR4B) && defined(CS41) && defined(WGM40)
	sbi(TCCR4B, CS41);		// set timer 4 prescale factor to 64
	sbi(TCCR4B, CS40);
	sbi(TCCR4A, WGM40);		// put timer 4 in 8-bit phase correct pwm mode
#endif
#endif /* end timer4 block for ATMEGA1280/2560 and similar */	

#if defined(TCCR5B) && defined(CS51) && defined(WGM50)
	sbi(TCCR5B, CS51);		// set timer 5 prescale factor to 64
	sbi(TCCR5B, CS50);
	sbi(TCCR5A, WGM50);		// put timer 5 in 8-bit phase correct pwm mode
#endif

#if defined(ADCSRA)
	// set a2d prescale factor to 128
	// 16 MHz / 128 = 125 KHz, inside the desired 50-200 KHz range.
	// XXX: this will not work properly for other clock speeds, and
	// this code should use F_CPU to determine the prescale factor.
	sbi(ADCSRA, ADPS2);
	sbi(ADCSRA, ADPS1);
	sbi(ADCSRA, ADPS0);

	// enable a2d conversions
	sbi(ADCSRA, ADEN);
#endif

	// the bootloader connects pins 0 and 1 to the USART; disconnect them
	// here so they can be used as normal digital i/o; they will be
	// reconnected in Serial.begin()
#if defined(UCSRB)
	UCSRB = 0;
#elif defined(UCSR0B)
	UCSR0B = 0;
#endif
}

Sull'Arduino, tutti i timer sono configurati in modalità Phase-PWM oppure Fast-PWM per generare un segnale di ~490 Hz oppure ~976 Hz, a seconda del timer ed avere già di default la possibilità di fare un analogWrite su tutti i pin segnali come PWM.
Quindi tutti i timer vengono preconfigurati. Se devi (non tu, dico in generale) reimpostare un timer, lo devi riconfigurare completamente perché altrimenti potresti lasciare dei flag dalla configurazione originale fatta dal core di Arduino. Inoltre quando dai un cli() tu non fai altro che disattivare gli interrupt globali, ossia dici alla MCU di ignorare tutti i segnali di interrupt generati, compresi quelli sollevati dai timer. Ma i timer continuano a girare. Per fermare materialmente un timer hai 2 modi: il primo è quello di togliergli il segnale di clock, così che il contatore non venga più incrementato, il secondo è quello di togliere materialmente l'alimentazione al timer, cioè "staccare" la spina agendo sul corrispondente pin del registro PRR.

Tornando alla delay. Questo è il suo codice:

void delay(unsigned long ms)
{
	uint16_t start = (uint16_t)micros();

	while (ms > 0) {
		if (((uint16_t)micros() - start) >= 1000) {
			ms--;
			start += 1000;
		}
	}
}

micros() conta i microsecondi con un meccanismo un pò incasinato basato sul valore del registro del timer 0 e del numero di overflow misurati (il timer 0 va in overflow con frequenza di 976 Hz, come il segnale PWM che genera).

Piuttosto, credo che il tuo problema derivi da questo:

TIMSK1 = (1 << OCIE1A) | (1 << TOIE1);

Perché attivi 2 interrupt? Quello cioè sul raggiungimento del valore di OCIE1A e quello sull'overflow? Impostando quello sul raggiungimento di OCR1A non credo che il timer scateni quello sull'overflow in modalità CTC con top fissato su OCR1A. Vado a naso, però. Non posso fare test, sono a lavoro ora.

L'interrupt di overflow è un rimasuglio di un copia incolla lasciato per sbaglio dal codice di un altro progetto.
Mi stupisce però che staccandogli il clock viene scatenato lo stesso, prova il codice che ho postato inizialmente dove con

TCCR1B = 0;

staccavo il clock ma... l'interrupt veniva ugualmente scatenato :fearful:

Con i timer non puoi lasciare "rimasugli" di precedenti setup :wink:
Soprattutto quando hai un DS da leggere che spesso non le dice tutte in modo chiaro. Ci sono casi estremi che non sono spesso spiegati bene. Il tuo rientra in uno di questi secondo me. Tu togli il clock al timer ma non spengi il timer. Hai un interrupt che setti ma non usi. Secondo me succedono conflitti.
Impostando la giusta ISR, tutto si risolve. Infatti questo codice a me pare funzionare:

ISR(TIMER1_OVF_vect) {
    asm __volatile__("nop\n\r");
}

ISR(TIMER1_COMPA_vect)        
{
  digitalWrite(7, HIGH);
  digitalWrite(7, LOW);  
}
void setup()
{
  delay(100);  
  pinMode(7, OUTPUT);    

  cli();    
  TCCR1A = 0;    
  TCCR1B = (1 << WGM12) | (1 << CS10);
  TCNT1 = 0;  
  OCR1A = 100;
  TIMSK1 = (1 << OCIE1A) | (1<<TOIE1);
  sei();  
  
  delay(3000);
  
  cli();
  TCCR1B = 0;  // Ferma Timer, INVECE non si ferma col delay da 100 iniziale
  sei();
}
void loop(){}

Molto probabilmente proprio perché definendo la ISR il vettore non salta a caso nel codice. Ora non ricordo di preciso, ma se un vettore non è definito, viene assegnato un indirizzo di default.

Mi sa che ci hai preso :wink: