generare una sequenza di bit a frequenza variabile

@menniti:
Cmq ricordati una cosa:

Floating point numbers are not exact, and may yield strange results when compared. For example 6.0 / 3.0 may not equal 2.0. You should instead check that the absolute value of the difference between the numbers is less than some small number.

Le operazioni sui virgola mobile non sono precise perché l'Atmega NON supporta nativamente questo tipo di dati e tutte le operazioni vengono effettuate convertendo i numeri in formato int per poi riportarli in formato float.

lesto:
notare le parentesi:((float)intero1) se non erro la divisione viene eseguita PRIMA del cast, quindi avresti una divisione tra interi (risultato con decimale troncato) trasformato in float (che quindi rimane 0, avendo già perso la parte decimale).

Sicuramente valida la regola che una parentesi in più oltre ad eliminare eventuali errori procedurali migliora la leggibilità del programma.
Però per quanto riguarda il cast questo ha la priorità, non bisogna mai scordarsi che wiring viene compilato tramite GCC, che a sua volta è un compilatore C e di conseguenza valgono tutte le regole del C indipendentemente dalle scorciatoie e semplificazioni che mette a disposizione wiring.

leo72:
Le operazioni sui virgola mobile non sono precise perché l'Atmega NON supporta nativamente questo tipo di dati e tutte le operazioni vengono effettuate convertendo i numeri in formato int per poi riportarli in formato float.

Questa è una cosa che vale per tutti i micro non dotati di una FPU o un core DSP, però non è vero che i numeri float vengono convertiti in interi prima del calcolo per poi essere riconvertiti in float, se così fosse sarebbe impossibile fare calcoli con la precisione fino alla sesta cifra decimale come normalmente avviene con i float a 32 bit.
Il discorso è molto complesso e come viene implementato il calcolo con i float dipende moltissimo dal compilatore stesso, esistono vari formati per rappresentare i float in binario ed esistono vari algoritmi di calcolo, alcuni molto precisi, ma lenti, altri imprecisi, ma veloci.
La realtà è che il valore float viene convertito in un formato intermedio che non ha nulla a che vedere con un normale valore intero (o long che sia), come avviene tale conversione dipende dallo standard usato, p.e. IEEE 754 che è quello usato dal C ANSI.
Il vero motivo per cui a volte si ottengono risultati bizzarri, tipo 7/3 = 2.333333458 invece di 2.3 periodico è dovuto agli errori cumulativi di arrotondamento che su un micro a solo 8 bit sono più frequenti che su un micro a 16/32 bit nativi.
Ti faccio alcuni esempi pratici di come vengono convertiti i numeri float dal GCC.
Una volta definita una variabile come float non importa se dentro ci metti un valore intero o con decimali sarà sempre trattata come se avesse i decimali, cosa ovvia altrimenti poi non sarebbe possibile aggiornarla con valori contenenti decimali in seguito a dei calcoli.

Usando la standard IEEE 754 a 32 bit il numero 100 scomposto nei quattro byte, da lsb a msb, che compongono il float diventa:

0x42 0xC8 0x00 0x00

il numero 100.25 diventa :
0x42 0xC8 0x80 0x00

Mentre il numero 1.58686699888 diventa:
0x3F 0xCB 0x1E 0x74

Se riconvertiamo i valori float nei numeri decimali per i primi due otteniamo nuovamente 100 e 100.25, per il terzo otteniamo 1.58686685562 perché con 32 bit al massimo abbiamo la precisione fino a sei cifre decimali.

Il formato IEEE 754, ma anche quasi tutti gli altri formati per i numeri float, converte i numeri in un formato esponenziale del tipo 1.xxxxxxxxxxxx ^ yy, infatti 100.25 viene convertito come 1.56640625^6 (unbiased) e ovviamente il formato binario equivalente contiene una parte di bit riservati all'esponente e una parte alla mantissa.
Il formato EEE 754 a 32 bit è il seguente:

SEEE EEEE EMMM MMMM MMMM MMMM MMMM MMMM

S = sign
E = biased exponent
M = mantissa
bias = -127

E', vero. Mi ero proprio dimenticato di questa cosa dell'esponente/mantissa.

@ astrobed:
sei un pozzo di scienza! mi sono riguardato quest'argomento su un testo di calcolatori, giusto per "fissarlo" con qualche esempio, spieghi meglio tu di Bucci!
@ lesto e leo72:
grazie per la soluzione ed i ragguagli: oggi la provo, riguardo la precisione, non devo realizzare uno strumento di lettura, bensì convertire il periodo in frequenza, in realtà mi basterebbero addirittura i soli interi (11364 (Hz), p.es., se poi ottengo un valore con 3 cifre decimali, e mi dite che sono abbastanza significative, ho fatto molto più di quanto mi serve. Vi ricordo che alla fine devo solo capire con che frequenza "indicativa" si ripete la sequenza che sto generando.
Certo questa disquisizione tornerà utilissima quando sarà necessaria, in altri casi, una lettura precisa, ma un buon frequenzimetro di rado mostra oltre le 5 cifre decimali, quindi hai voglia ad accontentarsi!
Infine mi sono reso conto di un errore a monte: per ottenere la frequenza in Hz devo prima rapportare i µs in s, quindi devo dividere seq_time per 1000000, poiché stiamo parlando di KHz, in realtà mi basta rapportarli a ms, dividendo per 1000. Pensavo quindi di fare:
Serial.println((float)1)/seq_time/1000;
il dubbio che ho: il solo float iniziale mi rende tutta l'espressione in virgola mobile? Inoltre vorrei far uscire tutto sulla stessa riga con l'indicazione della parola "frequenza" e l'unità di misura, penso mi basti "giocare" con , e ;
Proverò oggi e vi faccio sapere come va a finire. Intanto inizio a fare qualche prova con uno shift da 8 bit ed un dip, in attesa dell'uovo di pasqua di uwe :slight_smile:
Grazie a tutti.

Premesso che l'unico modo semplice per ottenere quello che desideri è usare degli shift register con ingresso seriale e uscita sia seriale che parallela (SIPO shift register) per ottenere un ring shift register della desiderata dimensione, però c'è un punto oscuro da risolvere prima di disegnare lo schema.
La frequenza di 10 MHz è riferita al singolo bit del pacchetto che devi trasmettere oppure è la frequenza di ripetizione di tutto il pacchetto ?

In effetti c'era confusione su questa cosa, poi mi sono chiarito le idee, e da lì è nata la discussione sulla misura della frequenza. I 10MHz riguardano la velocità alla quale devono essere "sparati" i singoli bit in seriale, considerando che sono una trentina (circa, come ho già spiegato) credo che l'intera sequenza si ripeta ogni 300-350KHz, quindi il clock sarà di 10MHz. Poiché carichiamo in seriale ad una velocità decisamente inferiore, bisognerebbe prima caricare lo shift e poi renderlo circolare (serve a questo la sezione parallela?), l'eventuale cambio sequenza per me rappresenta una prova ex novo.
Aspetto aiuti. Grazie.

menniti:
Serial.println((float)1)/seq_time/1000;

a parte che è sbagliata per via della parentesi del println che si chiude prima delle divisioni (quindi in pratica stampi 1 in float e poi provi a dividere una funzione, credo che il compilatore si incavoli un pò :slight_smile:

al solito... se non erro il compilatore, in caso di pari priorità degli operatori, parte da DESTRA.
quindi prima fa seq_time/1000
e poi 1/quelchel'è
come vedi però hai perso precisione per la storia degli interi nella /1000, quindi o usi i cast o fai 1000.0

per evitare mille parentesi e cast inutili (che vengono fatti a runtime, se non erro, facendo perdere qualche ciclo CPU) fai:
Serial.println( 1.0/seq_time/1000.0 );
e vai sicuro

Ho risolto così:
Serial.print("Tempo della sequenza: ");
Serial.print(seq_time);
Serial.print("Frequenza (KHz): ");
Serial.println(1.0/(seq_time/1000.0));
quindi l'operazione viene fatta regolarmente da sx verso dx (se non metto le () alla 2a divisione mi dà 0.
Ma il risultato è con 2 decimali, ne volevo almeno 3, probabilmente non ho capito qualcosa nella discussione precedente. :~

I due decimali sono un limite della serial.println, in realtà il numero ha tutti i decimali del caso, per vederne di più moltiplica il valore per 10-100-1000 e poi fai la serial.println, ovviamente dovrai riposizionare mentalmente la virgola.
Esempio pratico se faccio 7/3 ottengo 2.33 se moltiplico per 1000 la variabile che contiene il risultato ottengo 2333.33

Come non detto, mi sono appena ricordato che basta specificare dopo la virgola il numero dei decimali desiderati, se scrivo Serial.println(a,4) e a contiene 32.12345 mi viene inviato 32.1234.

Perfetto, ora funziona bene; speriamo che troviate anche il tempo di aiutarmi col circuito esterno.
Grazie

Per il momento prendilo come schema di principio, è una cosa che ho buttato giù al volo, ma dovrebbe andare bene.
Le tre linee in ingresso se lasciate a 1 logico fanno funzionare lo ring shift register come serve a te, portando Enable a 0 logico viene bloccato sia il clock a 10 MHz che il ricircolo dei dati, ogni volta che mandi un impulso negativo sulla linea clock il valore presente su DATA viene trasferito all'interno dello shift register, attenzione che vengono negati quindi o lo prevedi da software oppure aggiungi un inverter.
Fornendo 30 impulsi sul clock carichi completamente lo shift register, è possibile variare la lunghezza del pacchetto modificando l'out per il ricircolo dati.
Gli shift register sono dei 54HC4094 (oppure 74HC4094) da 8 bit l'uno, le porte logiche sono contenute in un singolo 54HC00, l'oscillatore è del tipo quarzato ibrido, ma puoi metterci quello che ti pare.

WOW! alla faccia del "volo"!
Allora alcuni chiarimenti: per caricare lo shift con i miei 30 impulsi da Arduino, visto che devo farne passare uno per volta, mi conviene abilitare un altro pin che porto a 0 prima di ogni bit della sequenza, e che collego all'ENABLE, dopo lo porto a 1, e così via; dopo il 30mo impulso devo però bloccare il loop, non potrei mettere il code sotto SETUP invece che sotto loop? In questo modo potrei tenere ENABLE a 0 per l'intera durata del ciclo oppure funziona su fronte e quindi serve proprio l'impulso?
Con questa tipologia di schema posso decidere di variare i bit della sequenza (avendo necessità di un range 25-32) semplicemente scegliendo una delle uscite Q1 (4) ÷ Q8 (11)? Potrei usare un dip commutando un solo pin per volta verso l'OUT.
Infine, per fare alcune prove oggi, dispongo di due comunissimi cd4094, di un cd4011 (nand, anche se con pinatura delle porte leggermente diversa), di un generatore di funzioni a 10MHz (ma penso che i CMOS non reggano questa frequenza, potrei provare a 1MHz); il dubbio che ho è se questi componenti possono andar bene per una simulazione con sequenza a 16 bit (o meno) e se i livelli logici dell'Arduino vengono riconosciuti dalle porte CMOS.
Che dici?
Grazie di tutto, mi hai fatto davvero un grande regalo!

menniti:
dopo il 30mo impulso devo però bloccare il loop, non potrei mettere il code sotto SETUP invece che sotto loop? In questo modo potrei tenere ENABLE a 0 per l'intera durata del ciclo oppure funziona su fronte e quindi serve proprio l'impulso?

Sarà che oggi ho la testa un pochino sconfusionata da un bel raffreddore, ma non ti seguo :slight_smile:
Il software su Arduino deve seguire una logica di questo tipo:
Nel setup inizializzi tutti i vari pin come serve.
All'interno del loop dovrai attendere un evento che causa il cambio sequenza, dato che non hai specificato come questo avviene posso solo ipotizzare che lo fai a intervalli fissi di tempo oppure tramite uno specifico comando premendo un pulsante o un carattere che arriva dalla seriale.
Comunque sia non devi fare altro che aspettare l'evento, cioè inserisci un controllo, e quando questo avviene chiami la funzione che genera l'eventuale sequenza random, oppure specifichi un array precaricato in memoria che contiene la sequenza, oppure la ricevi dalla porta seriale, dopo di che chiami la funzione che la carica nello shift register.
La funzione che carica la sequenza non deve fare altro che eseguire queste operazioni partendo dal presupposto che le tre linee di controllo, ENABLE, DATA, CLOCK, si trovano tutte quante 1 logico durante il normale funzionamento dello shift register:

  1. portare ENABLE a 0 logico
  2. mettere su DATA il valore caricare prelevandolo dall'array che contiene la sequenza.
  3. Portare CLOCK da 1 a 0
  4. Attendere 10us
  5. Portare CLOCK da 0 a 1
  6. Ripetere dal punto 2 per tante volte quanti sono i bit da caricare.
  7. Riportare DATA, CLOCK, ENABLE a 1 logico per riavviare lo shift register.

In pratica nella funzione che carica la sequenza usi una for(i=0;i<n;i++) ove n è il numero dei bit da caricare.

Con questa tipologia di schema posso decidere di variare i bit della sequenza (avendo necessità di un range 25-32) semplicemente scegliendo una delle uscite Q1 (4) ÷ Q8 (11)? Potrei usare un dip commutando un solo pin per volta verso l'OUT.

Si, in teoria puoi avere da 2 a 32 bit, basta che cambi il bit usato per il ricircolo e per l'out.

Infine, per fare alcune prove oggi, dispongo di due comunissimi cd4094, di un cd4011 (nand, anche se con pinatura delle porte leggermente diversa), di un generatore di funzioni a 10MHz (ma penso che i CMOS non reggano questa frequenza, potrei provare a 1MHz)

Direi che hai tutto quello che serve, la massima frequenza di lavoro dipende dalla serie, se sono i vecchi CD4xxx sarà difficile che arrivi oltre i 5 MHz, però per provare va bene lo stesso.

il dubbio che ho è se questi componenti possono andar bene per una simulazione con sequenza a 16 bit (o meno) e se i livelli logici dell'Arduino vengono riconosciuti dalle porte CMOS.

Anche se le soglie di tensione per 1 e 0 sono leggermente diverse tra l'ATmega e i CMOS della serie CD4xxx non dovrebbero esserci problemi.

Non è il raffreddore, sono io...
Dunque la parte elettronica ora mi è chiara. I problemi sono sulla parte Arduino (ancora non ci ho capito molto, come ben vedi!)
Il mio sistema deve funzionare in modo semplice e manuale:
Carico il code su Arduino, lo invio al tuo circuito, stop, a quel punto il tuo circuito dovrebbe vivere di vita propria e l'altro che ho realizzato (il riconoscitore di sequenze) legge i dati provenienti da esso e mi avvisa quando riconosce una delle sequenze che sto cercando; quindi non ho comandi automatici da mandare o controllare; la sequenza la imposto su Arduino e mi vale per tutta la sperimentazione; se decido di cambiarla semplicemente rimando il code ad Arduino e tutto ricomincia daccapo. Spero di essere stato più chiaro così. :astonished:
Ho provato a tradurre le tue indicazioni

#define DATA 12
#define ENABLE 13
#define CLOCK 7
byte sequenza[] = {
  1, 1, 1, 0, 1, 1, 0, 0};

void setup() {                
  pinMode(DATA, OUTPUT);  // attivo le uscite
  pinMode(ENABLE, OUTPUT);
  pinMode (CLOCK, OUTPUT);
  digitalWrite(ENABLE, LOW); //blocco tutto
  digitalWrite(DATA, LOW);
  digitalWrite(CLOCK, LOW);
}

void loop() 
{
  for(int i=0; i<8; i++)  //ciclo per 8 bit
  {
    digitalWrite(ENABLE, LOW); 
    digitalWrite(DATA, sequenza[i]);
    digitalWrite(CLOCK, LOW);
    delay (10);
    digitalWrite(CLOCK, HIGH);
  }
  digitalWrite(ENABLE, HIGH); //riavvio tutto
  digitalWrite(DATA, HIGH);
  digitalWrite(CLOCK, HIGH);
}

Il mio dubbio è che in questo stesso thread mi hano spiegato che ciò che è nella sezione loop si ripete all'infinito, ma questo mi creerebbe problemi; io vorrei eseguire tutto una sola volta in modo da non "disturbare" più il tuo circuito. Mi spieghi come fare e soprattutto se quando ho postato è corretto?

Meglio così :slight_smile:

#define DATA 12
#define ENABLE 13
#define CLOCK 7
byte sequenza[] = {
  1, 1, 1, 0, 1, 1, 0, 0};

void setup() {                
  pinMode(DATA, OUTPUT);  // attivo le uscite
  pinMode(ENABLE, OUTPUT);
  pinMode (CLOCK, OUTPUT);
  digitalWrite(ENABLE, LOW); //blocco tutto
  digitalWrite(DATA, HIGH);
  digitalWrite(CLOCK, HIGH);
}

void loop() 
{
  digitalWrite(ENABLE, LOW); // fermi ciclo shift
 
 for(int i=0; i<8; i++)  //ciclo per 8 bit
  {
    digitalWrite(DATA, sequenza[i]);
    digitalWrite(CLOCK, LOW);
    delay (10);
    digitalWrite(CLOCK, HIGH);
  }

  digitalWrite(ENABLE, HIGH); //riavvio tutto
  digitalWrite(DATA, HIGH);
  digitalWrite(CLOCK, HIGH);

  while(1);  // loop infinito e blocca il ciclo.

}

Naturalmente....
Grazie di tutto, ormai devo lasciare il mio angolo di pace (ho un piccolo lab fuori casa :D) e tornare alla cruda realtà (moglie, mutuo, ecc =(), posso continuare a gironzolare sul forum, ma per le prove ormai se ne parla nei prox giorni; naturalmente appena ho finito il mini-circuito posto i risultati, poi realizzerò quello completo con i componenti che mi hai indicato. Siete stati tutti davvero molto disponibili, a te un ringraziamento particolare per non avermi mandato un virus della rabbia invece del circuito...mi armerò della tua pazienza prima di rientrare :wink:

Carissimo Astrobeed,
funziona alla grande! Per il momento ho testato con due soli cd4094 e un cd4011, in configurazione "full", cioè sfruttando tutti i 16 bit, in quanto prelevo dal pin 9 del secondo Shift; in effetti la frequenza max è di 3MHz, ma dipende dai cmos, come prevedibile.
Confermando quanto dicevi lo stato dei bit è invertito, poiché non ho porte disponibili mi "secca" mettere un inverter nel circuito finale, poiché mi viene scomodo "ragionare" al contrario per inserire la sequenza, ho pensato di invertirla via soft; mi sembrava una cosa facile :astonished: col seguente codice:

for(int k=0; k<16; k++)
{ if (sequenza [k] == 0)
(sequenza [k] = 1);
else
(sequenza [k] = 0);
}

invece non funziona per niente :. mi trasforma la sequenza in un'altra che non c'entra niente, ho fatto decine di prove (ho provato a metterla sia in setup che in loop ma non cambia nulla); comunque se non riesco metto l'inverter, però mi pare davvero una cosa banale, dove sbaglio?
In ogni caso moltissime grazie, già solo questo problema risolto mi vale l'acquisto dell'Arduino.

Anche se hai scelto il modo più "macchinoso" per farlo mi sembra corretto, prova a inserire queste righe di codice in setup:

for(int k=0; k<16; k++)
   {
      sequenza[i] ^= 1;
   }

Eseguire l'Xor con 1 di un valore logico lo inverte e viene usata una singola istruzione assembler.