Leggere porta durante tone()

Ciao a tutti.
Così comincia a funzionare, ma con un attiny 85 ho un ritardo di 200uS...
Vorrei leggere lo stato di una porta durante un tone() per impostare un altro pin in controfase:

if(P)
  {
  P=0;
  t=millis();
  PORTB|=0b00000001; // Accende il LED su I/O0 = PB0
  LED=1;
  tone(4,1000,5); // Fa TIC nell'auricolare.
  }

B=PINB &0x10;
if(B!=Bo)
  {
  Bo=B;
  if(B==0x00) PORTB|=0x08;
  else PORTB&=0xF7;
  }

if(millis()-t>=10 && LED==1) {LED=0; PORTB&=0b11111110;} //  Spegne il LED su I/O0 = PB0

Sembra proprio che il tone() per avviarsi impieghi un bel po'... Adesso faccio qualche misura.

Grazie
Gianluca

Ebbene: il tone() impiega 3,5mS! Praticamente blocca tutto per 3,5mS, come un delay()! Uhmm...

  PORTB|=0b00000001; // Accende il LED su I/O0 = PB0
  tone(4,2000,500); // Bip acuto a ogni lampo.
  PORTB&=0b11111110; //  Spegne il LED su I/O0 = PB0
  delay(1000);

Datman:
Ebbene: il tone() impiega 3,5mS! Praticamente blocca tutto per 3,5mS, come un delay()! Uhmm...

... vai a vedere i sorgenti nel core così ti rendi conto delle cose che fa ... :smiley: :smiley: :smiley:

Guglielmo

Sì, prima ho dato un'occhiata, ma non mi sembrava che potesse impiegare 3,5ms! Inoltre, non capisco perché vedevo solo 200us... Con il programma originale impiega 200us, mentre con quello di test impiega 3,5ms!

Mah… Va bene… Era per avere un’uscita a ponte con il doppio della tensione picco-picco e senza componente continua, ma ve bene anche così. :slight_smile:
L’ho condiviso anche per stimolare un po’ con qualcosa di diverso dal solito. :slight_smile:

Non ho ben capito cosa vuoi ottenere … vuoi un pin che segua l’andamento di un altro pin ma invertito?

Se SI ti basta portare l’uscita del pin all’ingresso di un “interrupt” che scatta per “CHANGE” e nella ISR fare l’inversione.

Guglielmo

Vuoi un pin che segua l'andamento di un altro pin ma invertito?

Sì.
L'uscita devo portarla all'ingresso dell'interrupt fisicamente o posso farlo da software? Cioè posso attivare l'interrupt tramite il cambiamento di stato di un bit di una porta di uscita? Ti ricordo che è un piccolo attiny85. Inoltre, mi sembra che sia disponibile un solo ingresso con l'interrupt e già lo sto usando per rilevare gli impulsi provenienti dal tubo Geiger. Posso usare il pcint che ha su tutti i pin...

Grazie!

Datman:
Ti ricordo che è un piccolo attiny85. Inoltre, mi sembra che sia disponibile un solo ingresso con l'interrupt e già lo sto usando per rilevare gli impulsi provenienti dal tubo Geiger. Posso usare il pcint che ha su tutti i pin...

Il pin lo devi collegare fisicamente, quanto ad usare i PCINT ... da provare ... ::slight_smile:

Non so se gira su ATtiny85 (... in teoria dovrebbe andare), ma per la gestione di tutti gli interrupts, ti consigli la libreria EnableInterrupt :wink:

Guglielmo

Sì, sto provando con il pcint, ma mi sono reso conto di un problema: quando il pin PB4 su cui agisce il tone() va a 1, io posso portare a 0 PB3; quando, però, torna a 0 non posso avere un interrupt che riporti PB3 a 1!

Datman:
Sì, sto provando con il pcint, ma mi sono reso conto di un problema: quando il pin PB4 su cui agisce il tone() va a 1, io posso portare a 0 PB3; quando, però, torna a 0 non posso avere un interrupt che riporti PB3 a 1!

Non ho capito ... perché non puoi ? Con l'interrupt CHANGE tu hai un interrupt sia quando il pin va HIGH che quando va LOW e nella ISR devi solo invertire lo stato del pin che controlli.

Guglielmo

Ovviamente! :slight_smile: Ma si stava facendo tardi e mia moglie mi lanciava continui NMI attraverso tutti i miei 4 sensori audiovisivi! :slight_smile:

Non capisco perché non funziona…
Un cambiamento di stato su PB4 deve produrre il cambiamento opposto su PB3. Sull’oscilloscopio, però, vedo che PB3 sta sempre basso.

/* Per ATTINY85:
1: I/O5 PB5 A0 RS 
2: I/O3 PB3 A3
3: I/O4 PB4 A2          - Uscita per auricolare per Geiger
4: GND
5: I/O0 PB0    MOSI SDA - Uscita per LED provapila/Geiger
6: I/O1 PB1    MISO     
7: I/O2 PB2 A1 SCLK SCL INT0 - Ingresso per sonda Geiger
8: Vcc
*/

#include<avr/sleep.h>
byte state;
volatile byte P;
int B;
int Bo;
byte LED=0;
unsigned long t=0;

void particella() {P=1;}

ISR(PCINT0_vect)
{
if(PINB &&0x10==0x00) PORTB|=0x08;
else PORTB &=0xF7;
}

void setup()
{
pinMode(0, OUTPUT); // Al LED.
pinMode(2, INPUT); // Dal transistor dal tubo Geiger.
pinMode(2, INPUT_PULLUP); // Pull-up per il collettore del transistor.
pinMode(4, OUTPUT); // All'auricolare.
pinMode(3, OUTPUT); // All'auricolare (copia invertita del 4).

GIMSK = 0x60;    // turns on external and pin change interrupts.
PCMSK = 0x10;    // turn on interrupts on pin PB4.
sei();           // set interrupts (enable).
   
tone(4,2000,100); // Bip acuto all'accensione.
PORTB|=0b00000001;
delay(700);
PORTB&=0b11111110;
delay(1000);
readVcc();

for(byte n=1; n<=state; n++)
   {
   PORTB|=0b00000001; // Accende il LED su I/O0 = PB0
   tone(4,2000,25); // Bip acuto a ogni lampo.
   delay(30);
   PORTB&=0b11111110; //  Spegne il LED su I/O0 = PB0
   delay(250);
   }
delay(350);

attachInterrupt(0, particella, FALLING);
}


void loop()
{
if(P)
  {
  P=0;
  t=millis();
  PORTB|=0b00000001; // Accende il LED su I/O0 = PB0
  LED=1;
  tone(4,1000,5); // Fa TIC nell'auricolare.
  }

if(millis()-t>=10 && LED==1) {LED=0; PORTB&=0b11111110;} //  Dopo 10ms spegne il LED su I/O0 = PB0
}

... hai verificato che la ISR venga richiamata ?

Guglielmo

if(PINB &&0x10==0x00) PORTB|=0x08;
else PORTB &=0xF7;
}

Se non ti ostinassi a scrivere codice ILLEGGIBILE ti saresti reso conto prima che questo if ha qualcosa che non va.

Perché non usare digitalRead()?
Perché non usare digitalWrite()?
Perché non usare la forma (1 << PDx) perlomeno?

:confused:

Grazie a entrambi.

Guglielmo: credevo proprio che il problema stesse nel fatto che non veniva chiamata…

SukkoPera: se il 5° bit di PINB è 0, imposta a 1 il 4° bit di PORTB; altrimenti imposta a 0 il 4° bit di PORTB. E’ sbagliato?..
Comunque per un singolo bit, in effetti, digitalRead e digitalWrite semplificano il lavoro.

ISR(PCINT0_vect)
{
if(!digitalRead(4)) digitalWrite(3, HIGH);
else digitalWrite(3, LOW);
}

Nell’altra forma verrebbe:

ISR(PCINT0_vect)
{
if(PINB&&1<<4==0) PORTB|=1<<3;
else PORTB&=0xFF-1<<3;
}

Esatto?

Datman:
SukkoPera: se il 5° bit di PINB è 0, imposta a 1 il 4° bit di PORTB; altrimenti imposta a 0 il 4° bit di PORTB. E’ sbagliato?..

Ecco, ti rendi conto che guardando il codice non è immediatamente chiaro su quali pin lavori? Non sarebbe meglio usare un metodo che renda il tutto più esplicito e che riduce al minimo il rischio di errori, senza dovere fare calcoli, quando possiamo risparmiarceli?

Datman:

ISR(PCINT0_vect)

{
if(!digitalRead(4)) digitalWrite(3, HIGH);
else digitalWrite(3, LOW);
}

Questo è giusto e chiaramente più leggibile, senza contare che permette al tuo codice di girare su un sacco di schiede, senza vincolarti ad un microcontrollore AVR. Ma si può migliorare ancora:

if (!digitalRead(4)) {
  digitalWrite(3, HIGH);
} else {
  digitalWrite(3, LOW);
}

Ovvero usando l’indentazione, che rende subito chiaro - anche visivamente - quali istruzioni sono subordinate a quali altre, e le parentesi graffe, facoltative in questo caso, ma che evitano errori qualora si decida di aggiungere ulteriori istruzioni in un secondo tempo senza troppa attenzione.

C’è anche una possibile miglioria “semantica” sul fatto che digitalRead() non ritorna un valore logico, per cui andrebbe testata esplicitamente con HIGH o LOW, ma questo è troppo anche per me :D, lasciamo stare.

Questa è quindi la forma che dovresti usare SEMPRE. L’unico caso in cui vale la pena di complicare le cose è quando servono performance. In questo caso è bene avvicinarsi all’hardware per velocizzare il tutto, arrivando eventualmente a scrivere direttamente codice in assembly, nella consapevolezza di perdere la portabilità e complicare leggibilità e manutenibilità.

Datman:
Nell’altra forma verrebbe:

ISR(PCINT0_vect)

{
if(PINB&&1<<4==0) PORTB|=1<<3;
else PORTB&=0xFF-1<<3;
}



Esatto?

No, perché le precedenze tra gli operatori non danno il risultato giusto in questo modo, devi fare:

if (!(PINB & (1 << PB4)))
  PORTB |= (1 << PB3);
else
  PORTB &= ~(1 << PB3);

Nota l’uso delle macro PBx e non dei singoli numeri e l’uso necessario delle parentesi, per forzare che lo shift avvenga prima dell’AND bitwise. Nota inoltre la negazione bitwise nell’ultima riga.

… E soprattutto nota l’uso della singola & nell’if, che è l’errore presente nel tuo codice originale, che avresti evitato usando digitalRead(). Capisci che ti complichi solo le cose, scrivendo codice come fai tu?

Infine, nota che nonostante siamo andati a basso livello, è subito chiaro su quale bit stiamo lavorando, senza possibilità di errore e senza alcuna penalizzazione in performance, dato che tutte queste costanti vengono calcolate a compile time.

(Modalità “Maestrina” OFF :D)

Grazie! :slight_smile:

Hai commesso un errore, però: hai messo senza aver messo prima ! :)

Grazie ancora!

Ho fatto qualche prova:

Con digitalRead e digitalWrite, su PB4 non escono più i “tic” di 5ms (dei burst di 5 cicli a 1000Hz), ma solo uno stretto impulso di 100us per ogni “tic” a cui, dopo 200us dalla fine, segue un impulso di 18us su PB3.

Vedo che digitalWrite contiene un cli()!!!

Con

if (!(PINB & (1<<PB4)))
 PORTB |= (1<<PB3);
else 
 PORTB &= ~(1<<PB3);

funziona, ma si ha un ritardo di 158us, che è un’eternità qualunque sia la frequenza di clock!..

AVR Burn-O-Mat legge CKSEL3...0: 0010 (gli zeri sono quelli "spuntati") con Divide by 8 (spuntato), perciò, secondo il datasheet (pag. 27), il clock è a 8/8=1MHz:

Internal Calibrated RC Oscillator Operating Modes
CKSEL[3:0] Nominal Frequency
0010(1) 8.0 MHz

Togliendo il DIV8 (quindi passando da 1MHz a 8MHz), il ritardo scende a 35us. :slight_smile: (ma è sempre un'eternità per un periodo di clock di 125ns!!!)
Nelle prime prove avevo misurato un assorbimento di 1,5mA a 1MHz e 5,7mA a 8MHz.
Sì: appena tolgo DIV8, l'assorbimento sale da 1,3mA a 5,3mA. Eppure il datasheet dice che l'assorbimento dovrebbe andare da 0,3mA a 1mA, quindi aumentare di 0,7mA!...
Uhm... Vedo che ci sono un bel po' di variabili che dipendono dal clock sull'assorbimento complessivo!

Datman:
Con digitalRead e digitalWrite, su PB4 non escono più i “tic” di 5ms (dei burst di 5 cicli a 1000Hz), ma solo uno stretto impulso di 100us per ogni “tic” a cui, dopo 200us dalla fine, segue un impulso di 18us su PB3.

Sì, ha senso, perché sono funzioni piuttosto lente, impiegano 3-4 us ad essere eseguite. Se per le tue esigenze sono troppo lente, allora ha senso usare l’accesso diretto alle porte, come già dicevo prima.

Datman:
Con

if (!(PINB & (1<<PB4)))

PORTB |= (1<<PB3);
else
PORTB &= ~(1<<PB3);



funziona, ma si ha un ritardo di 158us, che è un'eternità qualunque sia la frequenza di clock!...

Boh, che sia indipendente dalla frequenza di clock mi pare un po’ strano, ma così su due piedi non ho nemmeno capito di che ritardo parli. Posso solo dire che è assolutamente la stessa cosa che avevi scritto tu:

if(PINB & 0x10==0x00) PORTB|=0x08;  // <-- & Corretto
else PORTB &=0xF7;
}

Solo scritta in maniera più leggibile, ma sono sicuro che il codice asm generato è esattamente lo stesso.