Scomposizione veloce di un numero in cifre

Ciao a tutti.

Avendo un valore numerico di 4 o 5 cifre, dovendo mutiplexare dei display a 7 segmenti, qual è il sistema più veloce per dividerlo in cifre? Ho fatto come segue, ma si può fare senza divisioni? Trasformando il valore in stringa, dividendo e ritrasformando in numeri è più veloce o più lento?

Grazie
Gianluca

cifra[1]=x/10000;
x=x%10000;
cifra[2]=x/1000;
x=x%1000;
cifra[3]=x/100;
x=x%100;
cifra[4]=x/10;
cifra[5]=x%10;

Non sono abbastanza ferrato per dire se sia più veloce la soluzione matematica o quella testuale. Comunque io avrei fatto come hai fatto tu.

Fallo a rovescio: scrivi dalla cifra 5 alla cifra 1, ogni volta dividendo e facendo modulo solo di 10, se lo fai in un ciclo for con tre righe te la cavi. Fai lo stesso numero di divisioni ma tutte per lo stesso divisore, forse il compilatore ottimizza, comunque passare per le stringhe certamente rallenta di molto, ammennocchè non ti servano direttamente i caratteri invece delle cifre

Ma se devi visualizzare solo numeri interi non ha molto senso trasformare in stringa.
Io l'ho fatto ma avevo altre necessità come ad esempio visualizzare il nome di parametri composto da 3 caratteri, es: StP, CtS ecc, dovevo sia visualizzare interi con e senza segno oltre a numeri con la virgola in quel caso ho dovuto fare in modo che il buffer del display accettasse solo codici corrispondenti a simboli visualizzabili e mi pare avessi solo 4 digit. Sicuramente è più rapido e meno costoso non usare conversioni da numero a stringhe.

Ciao.

Non so... dalle cifre ai caratteri c'è di messo solo una tabella, non una divisione!
Sto facendo delle prove. Il programma che ho pubblicato impiega 148us.
Purtroppo mi sono messo a combattere con un 328p che anni fa avevo impostato per errore su clock esterno e che ora ho recuperato mandandogli 16MHz esterni e sono rimasto indietro con l'esperimento!

Uhmm... Avevo sbagliato qualcosa... Forse avevo conteggiato anche la scrittura seriale o sul display!

byte cifra[6];
uint32_t x=12345;
uint32_t t;

void setup() 
{
Serial.begin(38400);
Serial.println();
uint32_t y=x;

t=micros();
cifra[1]=y/10000;
y=y%10000;
cifra[2]=y/1000;
y=y%1000;
cifra[3]=y/100;
y=y%100;
cifra[4]=y/10;
cifra[5]=y%10;
t=micros()-t;
Serial.print(t); Serial.println("us");
for(byte n=1; n<=5; n++) {Serial.print(cifra[n]); Serial.print(' ');}

Serial.println();
Serial.println("Char:");

char cifra2[6] = {0};
t=micros();
sprintf(cifra2, "%d", x);
t=micros()-t;
Serial.print(t); Serial.println("us");
Serial.println(cifra2);
}

void loop() 
{
}

Risultati:

00:33:59.311 -> 4us
00:33:59.311 -> 1 2 3 4 5 
00:33:59.311 -> Char:
00:33:59.311 -> 108us
00:33:59.311 -> 12345

E' molto più rapido il calcolo matematico.

io avevo pensato in questa forma:


unsigned long int n = 12345;
unsigned long int x = n;
char a[(sizeof x) + 1] = {0};

void setup(void)
{
    for (int i = 0; i < sizeof n; i++)
    {
        a[i] = ' ';
    }

    // il lavoro vero:
    {
        // ambiente locale per le variabili
        int i = sizeof x;

        while (x && i) // così garantisce anche la soppressione degli zeri superflui
        {
            a[i] = x % 10;
            x = x / 10;
            i--; // conteggio delle cifre
        }
    }
    // fine del lavoro vero
}
void loop(void)
{
}

la parte di scomposizione è tra le righe "lavoro vero"

Senza neppure fare prove, attraverso stringhe sarà più lento.
Però... poi come poi devi usare le singole cifre?
Ti servono numeri o char?

Grazie a tutti

Speravo che ci fosse qualche metodo geniale per farlo... :slight_smile:
Ho usato il modulo e funziona. Ho dovuto armeggiare un po' con il multiplexer, perché faceva battimento con i 100ms di misura dell'interrupt, pur avendo inserito l'mpx() anche durante quei 100ms di attesa. Tra poco vi faccio vedere il risultato.

Quei 4µs non mi convincevano, così ho voluto misurare col frequenzimetro:

// 150052.4 Hz (6.66 µs ciclo base senza operazioni)
L010:
    digitalWrite(13, 1);
    digitalWrite(13, 0);
    goto L010;
// 8611.4 Hz (116 µs procedura Datman)
L010:
    digitalWrite(13, 1);
        cifra[1] = y / 10000;
        y = y % 10000;
        cifra[2] = y / 1000;
        y = y % 1000;
        cifra[3] = y / 100;
        y = y % 100;
        cifra[4] = y / 10;
        cifra[5] = y % 10;        
    digitalWrite(13, 0);
    goto L010;
// 22820.1 Hz (43.8 µs procedura mia)
L010:
    digitalWrite(13, 1);
        cifra[5] = y % 10;
        y = y / 10;
        cifra[4] = y % 10;
        y = y / 10;
        cifra[3] = y % 10;
        y = y / 10;
        cifra[2] = y % 10;
        cifra[1] = y / 10;
    digitalWrite(13, 0);
    goto L010;

Al netto del tempo del ciclo base abbiamo 109.4 µs procedura Datman e 37 µs la mia, però non mi spiego perché con lo stesso identico numero di divisioni e moduli si ottengano risultati così differenti.

Se usi PORTB|=1<<PB5
e PORTB&=~(1<<PB5)
al posto dei digitalWrite che risultati ottieni?

2.65 MHz ciclo base.
9104.5 Hz procedura Datman
26642.5 Hz procedura mia

La durata netta delle procedure rimane la stessa, 109.4 datman, 37 la mia.

Misurando con micros(), con il calcolo al contrario sono comunque 4us...
Provo con il frequenzimetro.

Forse perché non cambiando mai il divisore...

1 Like
byte cifra[6];
char cifra2[6] = {0};
uint16_t x=12345;
/*
 DIRETTO: 1   13,123kHz (76,2us/2 = 38,1us/ciclo)
 INVERSO: 2   37,869kHz (26,4us/2 = 13,2us/ciclo)
STRINGHE: 3   4,995kHz (200,2us/2 = 100,1us/ciclo)
*/
uint8_t METODO=2;

void setup() 
{
pinMode(13, OUTPUT);

if (METODO==1)
  while(1)
  {
  cifra[1]=x/10000;
  x=x%10000;
  cifra[2]=x/1000;
  x=x%1000;
  cifra[3]=x/100;
  x=x%100;
  cifra[4]=x/10;
  cifra[5]=x%10;  
  PINB |=(1<<PB5);
  }

else if (METODO==2)
  while(1)
  {
  cifra[5]=x%10;
  x=x/10;
  cifra[4]=x%10;
  x=x/10;
  cifra[3]=x%10;
  x=x/10;
  cifra[2]=x%10;
  cifra[1]=x/10;  
  PINB |=(1<<PB5);
  }      

else if (METODO==3)
  while(1)
  {
  sprintf(cifra2, "%d", x);
  PINB |=(1<<PB5);
  }   
}

void loop() {}

1: 13,123kHz (76,2us/2 = 38,1us/ciclo)
2: 37,869kHz (26,4us/2 = 13,2us/ciclo)
3: 4,995kHz (200,2us/2 = 100,1us/ciclo)

Ecco il risultato, come promesso :slight_smile:

Note:

  1. 50kHz/64=781,25Hz. Nel mio DDS ho aggiunto un passo apposito per questo scopo.
  2. Power Grid Monitor.

Risultati troppo diversi, ho rifatto le misure generando un'onda quadra invece di impulsi e adesso corrispondono alle tue, probabilmente il frequenzimetro con segnali troppo asimmetrici si perdeva qualcosa.

// 13.01 µs (tolta la durata delle digital read/write)
  L010:
  cifra[5]=x%10;
  x=x/10;
  cifra[4]=x%10;
  x=x/10;
  cifra[3]=x%10;
  x=x/10;
  cifra[2]=x%10;
  cifra[1]=x/10;  
  digitalWrite(13, !digitalRead(13));
  goto L010;
// 37.91 µs (tolta la durata delle digital read/write)
  L010:
  cifra[1]=x/10000;
  x=x%10000;
  cifra[2]=x/1000;
  x=x%1000;
  cifra[3]=x/100;
  x=x%100;
  cifra[4]=x/10;
  cifra[5]=x%10;
  digitalWrite(13, !digitalRead(13));
  goto L010;
// 99.64 µs (tolta la durata delle digital read/write)
  L010:
  sprintf(cifra2, "%d", x);
  digitalWrite(13, !digitalRead(13));
  goto L010;

La misura con micros non è quindi affidabile...

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.