Come possiamo stimare approssimativamente la frequenza massima degli impulsi con il codice sopra
può gestire per leggere lo stato corretto?
Leggerà sempre lo stato correttamente. La domanda che penso tu stia cercando di porre è qual è la frequenza massima che può "misurare" senza perdere nessun alto o basso.
Dovremmo trovare quanti cicli di clock utilizza e moltiplicarli per
frequenza di clock?
Fondamentalmente sì. Il fattore importante è il tempo tra ogni lettura della porta. Tieni presente che, a seconda di ciò che fa il codice macchina, potrebbe non essere sempre lo stesso, quindi dovresti utilizzare il tempo massimo tra le letture.
E se sì, come posso farlo in pratica?
È possibile disassemblare il codice e calcolare quanto tempo impiega ciascuna istruzione, o esaminarla nel simulatore, o eseguire il codice in un ATmega328p effettivo e monitorare l'output fisico (che potrebbe essere ad es. visualizzazione della frequenza su uno schermo LCD).
Notare che i risultati dipendono in modo critico dal codice macchina generato dal compilatore. Con le ottimizzazioni, tutte le variabili che non contribuiscono all'output possono essere ottimizzate e altre modifiche apparentemente banali possono avere un grande effetto sulla quantità di codice generato. Pertanto l'unico modo accurato garantito per testare il codice è nella sua interezza. L'esecuzione di piccoli frammenti di codice isolato può dare un'idea molto fuorviante delle prestazioni dell'applicazione finita.
Ad esempio, ecco l'elenco del codice nella tua domanda: -
int main (void) {
86: 89 e1 ldi r24, 0x19; 25
88: 90 e0 ldi r25, 0x00; 0
uint8_t val;
for (int i = 0; i<25; i ++) {
dati << = 1;
PORTD & = ~ (1 << 5);
8a: 5d 98 cbi 0x0b, 5; 11
// _delay_us (2);
PORTD | = (1 << 5);
8c: 5d 9a sbi 0x0b, 5; 11
// _delay_us (2);
if ((PIND & (1 << PIND6)) == (1 << PIND6)) {
8e: 29 b1 in r18, 0x09; 9
90: 01 97 sbiw r24, 0x01; 1
while (1) {
uint8_t val;
for (int i = 0; i<25; i ++) {
92: d9 f7 brne.-10; 0x8a <main + 0xa>
94: f8 cf rjmp.-16; 0x86 <main + 0x6>
Nessun codice viene generato per val
e data
e il ciclo interno ha solo 5 istruzioni che richiedono 9 cicli. Con un clock a 16 MHz, il tempo di loop interno è 62,5 ns * 9 = 562,5 ns, che dovrebbe essere in grado di tenere il passo con una frequenza di ingresso di ~ 888 kHz.
Successivamente metto in output data
in PORTD, che forza il compilatore a generare il codice per esso: -
while (1) {
uint8_t val;
for (int i = 0; i<25; i ++) {
dati << = 1;
90: 88 0f aggiungi r24, r24
92: 99 1f adc r25, r25
94: aa 1f adc r26, r26
96: bb 1f adc r27, r27
PORTD & = ~ (1 << 5);
98: 5d 98 cbi 0x0b, 5; 11
// _delay_us (2);
PORTD | = (1 << 5);
9a: 5d 9a sbi 0x0b, 5; 11
// _delay_us (2);
if ((PIND & (1 << PIND6)) == (1 << PIND6)) {
9c: 49 b1 in r20, 0x09; 9
}
altro {
val = 0;
}
dati | = val;
9e: 46 fb bst r20, 6
a0: 44 27 eo r20, r20
a2: 40 f9 bld r20, 0
a4: 84 2b o r24, r20
a6: 21 50 subi r18, 0x01; 1
a8: 31 09 sbc r19, r1
while (1) {
uint8_t val;
for (int i = 0; i<25; i ++) {
aa: 91 f7 brne.-28; 0x90 <main + 0x10>
}
dati | = val;
}
PORTD = (uint8_t) dati;
ac: 8b b9 out 0x0b, r24; 11
// Il resto del codice
}
ae: ee cf rjmp.-36; 0x8c <main + 0xc>
Il ciclo interno ora ha 14 istruzioni che richiedono 17 cicli e la frequenza massima che può seguire accuratamente è quasi dimezzata.
Infine rendo statici i data
per costringere il compilatore a salvarli in memoria (cosa che potrebbe essere richiesta per un programma più complesso): -
while (1) {
uint8_t val;
for (int i = 0; i<25; i ++) {
dati << = 1;
9a: 40 91 00 01 lds r20, 0x0100; 0x800100 <_edata>
9e: 50 91 01 01 lds r21, 0x0101; 0x800101 <_edata + 0x1>
a2: 60 91 02 01 lds r22, 0x0102; 0x800102 <_edata + 0x2>
a6: 70 91 03 01 lds r23, 0x0103; 0x800103 <_edata + 0x3>
aa: 44 0f aggiungi r20, r20
ac: 55 1f adc r21, r21
ae: 66 1f adc r22, r22
b0: 77 1f adc r23, r23
b2: 40 93 00 01 punti 0x0100, r20; 0x800100 <_edata>
b6: 50 93 01 01 punti 0x0101, r21; 0x800101 <_edata + 0x1>
ba: 60 93 02 01 punti 0x0102, r22; 0x800102 <_edata + 0x2>
essere: 70 93 03 01 m 0x0103, r23; 0x800103 <_edata + 0x3>
PORTD & = ~ (1 << 5);
c2: 5d 98 cbi 0x0b, 5; 11
// _delay_us (2);
PORTD | = (1 << 5);
c4: 5d 9a sbi 0x0b, 5; 11
// _delay_us (2);
if ((PIND & (1 << PIND6)) == (1 << PIND6)) {
c6: 29 b1 in r18, 0x09; 9
}
altro {
val = 0;
}
dati | = val;
c8: 26 fb bst r18, 6
ca: 22 27 per r18, r18
cc: 20 f9 bld r18, 0
ce: 40 91 00 01 lds r20, 0x0100; 0x800100 <_edata>
d2: 50 91 01 01 lds r21, 0x0101; 0x800101 <_edata + 0x1>
d6: 60 91 02 01 lds r22, 0x0102; 0x800102 <_edata + 0x2>
da: 70 91 03 01 lds r23, 0x0103; 0x800103 <_edata + 0x3>
de: 42 2b o r20, r18
e0: 40 93 00 01 punti 0x0100, r20; 0x800100 <_edata>
e4: 50 93 01 01 punti 0x0101, r21; 0x800101 <_edata + 0x1>
e8: 60 93 02 01 punti 0x0102, r22; 0x800102 <_edata + 0x2>
ec: 70 93 03 01 punti 0x0103, r23; 0x800103 <_edata + 0x3>
f0: 01 97 sbiw r24, 0x01; 1
while (1) {
uint8_t val;
for (int i = 0; i<25; i ++) {
f2: 99 f6 brne.-90; 0x9a <main + 0xa>
f4: d0 cf rjmp.-96; 0x96 <main + 0x6>
Il codice del ciclo interno è ora gonfiato a 29 istruzioni che richiedono 49 cicli, riducendo la frequenza massima misurabile a ~ 163 kHz. La semplice aggiunta della parola chiave static
è stata sufficiente per renderlo 5 volte più lento. Ma questa è la velocità realistica che potresti aspettarti quando il codice viene utilizzato in un'applicazione più grande.
Se hai bisogno della massima velocità possibile indipendentemente dalle stranezze del compilatore, hai 3 opzioni: -
-
Scrivi codice assembler finemente elaborato che utilizzi ciascuna istruzione nel modo più efficiente possibile (altro codice non critico può ancora essere scritto in C).
-
Utilizza hardware periferico come l'unità timer / contatore o SPI.
-
Aggiungi un chip esterno come un prescaler per dividere la frequenza o un registro a scorrimento (es. CD4031) per catturare la forma d'onda.