Forum: Mikrocontroller und Digitale Elektronik int in BCD


von Josef (Gast)


Lesenswert?

Hallo ihr lieben Leut !

Weis jemand, wie man eine int Variable auf schnellste Weise in zB 5 BCD
Zahlen umwandelt (in C, für zB. eine Ausgabe in ein LCD-Display) ?

Schöne Grüße Josef

von Jens123 (Gast)


Lesenswert?

puhh in c keine ahnung, aber in asm kann ichs dir sagen =)


du nimst die zahl, subtrahierst so lannge 10, bis diese kleiner 10 ist
also 34 -10 - 10 - 10 = 4
beim teilen hast du 3 schleifendurchgaennge byte1=3 und den rest
byte0=4

ich meine in C gibts da aber eine funktion fuer um sowas zu machen

Gruss Jens

von Matthias (Gast)


Lesenswert?

Hi

am schnellsten? Look-Up-Tabelle. Dann sind aber ~ 300k Speicher weg
:-)

Ansonsten halt immer wieder durch 10 Teilen und den Rest der Divisionen
speichern.

Matthias

von Michael (Gast)


Lesenswert?

Ich glaube mit dem % (Modulus) Operator. 34 % 10 = 4; 4 ist der
ganzzahlige Rest der Division. 34 / 10 = 3; So müßte es gehen.
Michael

von Peter Kasi (Gast)


Lesenswert?

Hier mal für 2 Stellen, hab ich irgendwo mal im Internet gefunden

#define FROMBCD(x)      (((x) >> 4) * 10 + ((x) & 0xf))
#define TOBCD(x)        (((x) / 10 * 16) + ((x) % 10))

von Peter D. (peda)


Lesenswert?

http://www.mikrocontroller.net/forum/read-4-46127.html#new

Noch schneller wäre gewesen, selber in der Codesammlung nachzusehen.


Peter

von Matthias (Gast)


Lesenswert?

Hi

ich persönlcih finde

/**
  Zahl dezimal ausgeben
  d:  auszugebende Zahl
  n:  Anzahl der Stellen (von hinten)
  v:  0=ohne Vornullenunterdrückung
*/
void rs232_printd(unsigned long d, unsigned char n, unsigned char v)
{
  unsigned char s[10];
  unsigned char m;

  //Erstmal Array mit 10 Stellen erzeugen
  for(m=0;m<10;m++)
  {
    s[m]=(d%10);
    d/=10;
  }
  m=0;

  do{
    n--;
    if((!v)||(m)||(s[n]))
    {
      rs232_putc(s[n]+0x30); m=1;
    }

  }while(n);
}

schöner. Compiliert (mit dem AVRGCC) sogar kleiner als Peters Variante
und unterstützt auch long-Zahlen. Allerdings keine vorzeichenbehafteten
Zahlen und ist ähnlich unleserlich :-)

Der Compiler (bzw. der Entwickler der Bibliothek) weiß oftmals besser
wie man dividiert als wenn man das selber implementiert.

Matthias

von heckmotor (Gast)


Lesenswert?

Hi,

ich hattes es mal so gemacht, war für einen PCF8563 bestimmt:


unsigned char bcd_to_dec(unsigned char value)
{

   unsigned char dummy  = 0;
   unsigned char dummy1 = 0;
   unsigned char dummy2 = 0;
   unsigned char output = 0;


  dummy  = value & 0x0F;
  dummy1 = value & 0x70;

  dummy2 = dummy1 >> 4;

  output = (dummy2 * 10) + dummy;

return (output);
}


unsigned char dec_to_bcd (unsigned char value)
{

       unsigned char dummy  = 0;
       unsigned char dummy1 = 0;
       unsigned char dummy2 = 0;
       unsigned char save_val = value;
       unsigned char output = 0;

  dummy = save_val %= 10;
  dummy1 = value / 10;

  dummy2 = dummy1 << 4;

  output = dummy | dummy2;

return (output);
}

von Josef (Gast)


Lesenswert?

Vielen Dank für die Antworten. Ich habe es so gemacht, aber das ist mit
0,25 ms zu lang.


//*******************int Zahl in BCD umrechnen und in Ram Array schr.
void Convert_Int_Bcd ( unsigned int Zahl)
        {
        unsigned int Help;


            Help  = Zahl /10000

            Seg_Dat[4]  = Convert_Seg (Help) ;

            Zahl = Zahl - (Help * 10000);
            Help  = Zahl / 1000;

            Seg_Dat[3]  = Convert_Seg (Help) ;

            Zahl = Zahl - (Help * 1000);
            Help  = Zahl / 100;
            Seg_Dat[2]  =  Convert_Seg (Help);

            Zahl = Zahl - (Help * 100);
            Help  = Zahl / 10;
            Seg_Dat[1]  =  Convert_Seg (Help);

            Zahl = Zahl - (Help * 10);
            Help  = Zahl;
            Seg_Dat[0]  =  Convert_Seg (Help);


        }


Schöne Grüße Josef

von Josef (Gast)


Lesenswert?

An Michael:

Eine schöne Lösung - aber Modulo ist leider noch langsamer.Habe es
probiert:

Counter = 1234;

       Temp = Counter % 10;
       Counter /= 10;
       Temp = Counter % 10;
       Counter /= 10;
       Temp = Counter % 10;
       Counter /= 10;
       Temp = Counter % 10;

Funktioniert gut - aber eben langsamer (0,24 ms)als mein umständlich
aussehender Code (0,19ms).
Vordefinierte Funktion gibt es für diese Funktion nicht (Codevision).
Dafür Unmengen an string Operationen. Die Einzige - die ich fand war
bcd_t_bin und bin_t_bcd. Die gehen aber nur bis 99.



Schöne Grüße Josef

von Philipp Sªsse (Gast)


Lesenswert?

Je nachdem, in welchem Bereich die Zahlen meistens liegen, lohnt sich
von den ersten "Zahl = " noch ein "if (Help)".

Dann ist Convert_Seg () hoffentlich ein Makro, sonst kannst Du alles
andere Gefeile vergessen! (-:

Dann weiß ich nicht wie intelligent der Compiler ist, aber auf einem
8-Bitter würde ich Help als unsigned char definieren (Vorsicht dann bei
"Help * 100")

Falls Du Platz hast und jede µs zählt, ist der LookUp-Table ürigens gar
nicht so abwegig, aber Byte-weise:


unsigned char High =(unsigned char) (Zahl >> 8);
unsigned char Low = (unsigned char) Zahl;

// LookUpHighx[] enthält ASCII-Ziffern
// LookUpLowx[] enthält Bytes von 0-9
Ziffer[0] = LookUpHigh0[High] + LookUpLow0[Low];
Ziffer[1] = LookUpHigh1[High] + LookUpLow1[Low];
Ziffer[2] = LookUpHigh2[High] + LookUpLow2[Low];
Ziffer[3] = LookUpHigh3[High];
Ziffer[4] = LookUpHigh4[High];

if (Ziffer[0] > '9')
  {
    Ziffer[0] -= 10;
    Ziffer[1]++;
  }
if (Ziffer[1] > '9')
  {
    Ziffer[1] -= 10;
    Ziffer[2]++;
  }
if (Ziffer[2] > '9')
  {
    Ziffer[2] -= 10;
    Ziffer[3]++;
    if (Ziffer[3] > '9')
      {
        Ziffer[3] -= 10;
        Ziffer[4]++;
      }
  }


Das Feld ist dann (5 + 3) * 256 Bytes = 2 kB und die Routine wohl auf
den meisten kleinen µC deutlich schneller.

von Jens123 (Gast)


Lesenswert?

Hier allerdings in ASM




convert:

ldi temp3, 0x00; 1te Stelle (1X)

convert_11:
cpi temp1, 0x0a
brsh convert_1

mov temp2, temp1; 2te Stelle (X)

ret


convert_1:
ldi temp, 0x0a
sub temp1, temp
inc temp3
jmp convert_11

Ohne Funktionen, die teilen muessen etc.. sollte sehr schnell gehen und
laesst sich erweitern

von Matthias (Gast)


Lesenswert?

Hi

wozu braucht man denn BIN->BCD? Wohl nur für irgendeine Displayausgabe
die der Mensch lesen soll. Intern macht diese Art der Zahlendarstellung
eigentlich keinen Sinn.

Und wie oft kann ein Mensch eine neu Zahl aufnehmen? Alle 0,1s? Wenn
die ganze Routine dann 1ms zur Konvertierung braucht sind das 1% der
CPU-Zeit.

Matthias

von Peter D. (peda)


Lesenswert?

"Vielen Dank für die Antworten. Ich habe es so gemacht, aber das ist
mit
0,25 ms zu lang."


Und warum probierst Du dann nicht die Subtraktionsmethode, die ich Dir
angegeben habe ???


Peter

von Josef (Gast)


Lesenswert?

Lieber Mathias !

Mann brauch so eine schnelle Umrechnung, wenn man ZB. ein
Siebensegmentdisplay mit 6 Stellen ansteuern will, dazu einen Drehgeber
auswerten muß und noch viele andere Sachen dazu.
Ich bekomme pro sec. 3000 Impulse auf 4 Kanälen die 90 Grad
phasenverschoben sind. Jetzt gilt es diese Impulse zu zählen, zu
dividieren (Fließkomma) und dann anzuzeigen. Parametereingaben
,Datensicherung bei Stromausfall, PWM Ausgänge,
Helligkeitsregelung, 4 Leds und noch 4 Taster sind auszuwerten.
Vernetzung der Zähler obligat. Der ATMega läuft mit Volldampf,
tut es gerade noch.


LG Josef

von Josef (Gast)


Lesenswert?

@Peter Danke teste es gerade

von Josef (Gast)


Lesenswert?

Ich habe es jetzt so probiert:

#include <stdio.h>
char buffer[32];

sprintf(buffer,"%ld",long_var);
lcd_puts(buffer);

Josef

von Stefan Kleinwort (Gast)


Lesenswert?

@Josef:
denk nochmal über Matthias seine Antwort nach - er hat schon Recht.
Wenn Du die BCD-Zahl wirklich nur für die Anzeige brauchst, dann musst
Du sie nicht so oft rechnen, dass diese Rechnung die cpu nennenswert
belastet. Du nanntest 0,25ms pro rechnung - das mal 10-20 mal Refresh
pro Sekunde ergibt nicht mehr als 0,5% Deiner cpu-Leistung.

Du musst Dich nur von der Vorstellung lösen, jeden neuen Wert subito
aufs Display zu bringen. So schnell sind unsere Augen nicht ...

Stefan

von Matthias (Gast)


Lesenswert?

Hi

@Josef
selbst 10 mal pro Sekunden sind zu schnell. Stell mal eine Zahl dar
deren hintere Stelle sich mit 10Hz ändert. Das kannst du kaum noch
erfassen. Ich habe schon so einige Steuerungen gebaut. Dort habe ich
anfangs auch mit Refreshraten für 7-Seg. Anzeigen von 0,1s gearbeitet.
Für jemanden der das ablesen soll oder die ganze Zeit im Augenwinkel
hat (weil in KfZ eingebaut) ist das extrem nervig.

Mitlerweile haben sich Refreshraten von 0,5s - 1s als brauchbar
herausgestellt.

Für eine schnelle Tendenzanzeig sind (LED) Balckenanzeigen geeigneter.

Wenn man natürlich die Umwandlung so auslegt das sie alle anderen
Prozesse blockiert muß man optimieren. Aber dann sollte man sich
erstmal Gedanken über sein Konzept machen.

Und wenn ich lese das du Fließkomma auf einem ATMega machst wundert es
mich überhaupt nicht mehr das du Geschwindigkeitsprobleme bekommst.

Es gibt nur sehr wenige Anwendungen die Flieskomma auf Maschinen die
das nicht Hardwaremäßig können rechtfertigen.

Matthias

von Peter D. (peda)


Lesenswert?

Da hat Matthias vollkommen recht.

Schau einfach mal auf Dein Multimeter, das macht etwa 2 ... 5 Meßwerte
je Sekunde.
Aber nicht, weil es nicht schneller geht, sondern weil es ergonomisch
ist.

Und 5 mal je Sekunde ein bischen Floating Point und Dezimalanzeige
lastet einen ATMega noch lange nicht aus.

Da ist also etwas grundlegend falsch in Deinem Programmablaufplan.


Peter

von Josef (Gast)


Lesenswert?

Wäre froh, wenn jemand eine bessere Idee hätte !

Aufgabenstellung zur Software:

Von einem Drehgeber kommen 2 (4) phasenverschobene Impulse.
Zählung dieser Impulse und Darstellung im 5-stelligen 7SEG.
Die Anzeige soll die zurückgelegete Wegstrecke permanent anzeigen.
Also: wenn der Schlitten fährt muß auch die Anzeige permanent
mitlaufen, damit der Anwender weiß, wo der Schlitten ist.
Max 3000 Impulse /sec. Drehrichtungserkennung über die
phasenverschobenen Drehgebersignale (Softentprellung), bei Stromausfall
Speicherung der akuellen Position. Die gezählten Impulse müßen über
einen einstellbaren Parameter (zB 22.8) geteilt werden, bis sie
angezeigt werden dürfen. Dimmung der Anzeige und Vernetzung. Kosten pro
Einheit ab 1000 Stück: 15 € !.

Eigentlich alles gelöst, bis auf  unser diskutiertes Problem.
Bevor jemand gute Tips gibt, bitte genau überlegen !


SG Josef

von Josef (Gast)


Lesenswert?

Man kann die Impulse im Interrupt zählen, aber sollte gerade ein Timer
Interrupt ablaufen (Segmentrefresh)hast du Impulsverluste (AVRs haben
keine Interruptpriorität, was sie eigentlich unbrauchbar macht - 8051er
haben das schon seit der 1.Stunde (ca. 22 Jahre)) und die Anzeige zeigt
falsche Werte an. Nur so zur Anregung ;-)


SG Josef

von Josef (Gast)


Lesenswert?

Wobei ihr Recht habt mit der Refreshrate......aber wann soll ich
umrechnen - und was passiert derweil...hm ?


Josef

von Matthias (Gast)


Lesenswert?

Hi

Mit welcher Frequenz läuft der AVR? Wenns nicht grade 1MHz ist kannst
du bei 3kHz doch per Timer-ISR (~30kHz bei C, mehr bei ASM) problemlos
überabtasten und die Erfassung per Software machen. Solcherlei
Drehgeber prellen eigentlich kaum und wenn doch so schnell das man das
per RC wegbügeln kann.

Displayrefresh und anderes machst du dann bequem im Mainloop evtl.
synchronisiert über ein Flag aus der Abtast-ISR.

Division durch 22,8 kann man einfach, je nach erforderlicher
Genauigkeit, mit Fixkomma erschlagen. z.B. Multiplikation mit 2874 und
anschließender "Divison" durch 256. Bleibt ein x.8 Fixkommaformat.
Integer-Multiplikation geht dank Hardware sehr schnell und Division
durch 2^n ist trivial.

Sollte mit einem AVR bei 8MHz keine große Sache sein.

Matthias

von Josef (Gast)


Lesenswert?

Danke Matthias. Der AVR läuft mit 8 Mhz. Habe vergessen zu erwähnen,
dass das Ding mit Batteriebetrieb läuft und min. 6 Monate halten muß.
Schalte die Taktung im Betrieb herunter (je nach Bedarf).


SG Josef

von Peter D. (peda)


Lesenswert?

Du glaubst doch nicht im Ernst, daß ein Mensch 3000 Zahlen/s ablesen
kann !!!

Also einfach die Impulse im Interrupt zählen und dann bequem im
Hauptprogramm ausgeben.

Der besseren Ablesbarkeit halber würde ich aber noch mindestens 100ms
warten, damit nicht schneller als 10 Werte/s ausgegeben wird, sonst
siehst Du beim untersten Digit nur noch flimmernde Achten.


Peter

von Josef (Gast)


Lesenswert?

Peter - du hast recht. Das Problem ist, das während der Umrechnung
und des Segmentrefreshes Impulse verloren gehen. Deswegen wäre ich mit
der Umrechnung nach Abschluß eines Impulses schon gerne fertig um den
nächsten Impuls zu pollen und die Drehrichtung zu erkennen.


Josef

von Peter D. (peda)


Lesenswert?

@Josef,

ja, Du must natürlich im Interrupt zählen.

In der Codesammlung ist ein Beispiel von mir. Für max 3000 Pulse dürfte
ein Timerinterrupt von 10Khz dicke ausreichen (800 Zyklen). Da hat also
der AVR noch reichlich Zeit für anderes.


Peter

von crazy horse (Gast)


Lesenswert?

hab gerade noch mal in alten Sachen gewühlt, im schlimmsten Fall (59999)
kann man mit 300 Takten (bei 8MHz=38µs) auskommen.
Was natürlich nichts dran ändert, dass man nicht jeden Wert anzeigen
muss oder sollte.

von Matthias (Gast)


Lesenswert?

Hi

@Peter
Für die Drehgeber reichen die 10kHz Abtastrate nicht. Da die zwei
Signale 90° phasenverschoben sind muß man mindestens 4-fach
überabtasten um keinen Flankenwechsel zu verlieren. Also mindestens mit
12kHz. Ich persönlich gehe dann lieber auf Nummer sicher und drehe die
Abtastfrequenz so hoch wie möglich.

Bei einem 4MHz AVR hab ich hier zwei Statemachines zur
Drehgeber-Auswertung und zwei Zeitgeber im 20kHz Timer-INT. Im
Timer-INT wird mit int-Zahlen gerechnet (alles in C). Braucht, ohne das
ich besonders auf Geschwindigkeit optimiert habe, etwa 50% CPU-Zeit. Da
bleibt für den Rest noch genug übrig und die Drehgeber werden sicher
ausgewertet. Deshalb auch mein Vorschlag mit den 30kHz. Sollte bei
einem 8MHz AVR kein Thema sein.

Matthias

von Michael (Gast)


Lesenswert?

Warum so etwas im Timerinterrupt? Ext. Int für eines der beiden Signale,
ein ordinärer Portpin für das um 90° Phasenversetzte. Im Interrupt den
Portpin abfragen und schon hast du die Richtung. Oder übersehe ich was
wichtiges?
Michael

von Thorsten (Gast)


Lesenswert?

Externe Interrupts sind störanfälliger, obwohl ich meine eigene
Auswertung auch mit zwei externen Interrupts realisiert habe. Die Sache
mit dem Timer habe ich noch nicht ganz durchblickt, und ich will das
selbst lösen und nicht einfach abkupfern.

Thorsten

von Matthias (Gast)


Lesenswert?

Hi

beim externen INT bekommst du Probleme wenn der Drehgeber auf einer
Position steht und er etwas "zappelt" (z.B. immer hin und her durch
Viberationen) Dann macht dir dein externer INT den Controller dicht
weil er schneller aufgerufen wird als du ihn abarbeiten kannst.

Der Timer-INT wirkt da sozusagen als Tiefpass -> Entprellung

Matthias

von Philipp Sªsse (Gast)


Lesenswert?

[Josef:]
> Das Problem ist, das während der Umrechnung
> und des Segmentrefreshes Impulse verloren gehen.

Nicht so einfallslos, bitte! Du mußt die Umrechnung nicht am Stück
machen. Wenn Du die Umrechnung im main-loop machen willst, dann etwa
so: bei Zyklus 1 wird der Counter-Wert gelatcht, bei 2 rechnest Du die
erste Stelle, bei 3 die zweite usw., nach der letzten kannst Du mit dem
Segmentrefresh anfangen, dann kannst Du x Zyklen auch gar nichts tun,
damit die Anzeige ablesbar bleibt. Auf die Art kannst Du praktisch
jedes Codebeispiel umbauen.

von Josef (Gast)


Lesenswert?

Neues Problem :
Mein Kunde benützt Billigst-Drehgeber mit Hall-Sensoren. Die Dinger
schicken Störungen daß einem fad wird (auch bei Stillstand) Ex.Int
scheidet nun aus. CPU läuft Amok. Nun kommen RC Glieder ins Spiel.
Dimensionieren der RC Glieder sehr kritisch.


 @Philipp : guter Ansatz -danke´!

Josef

von Stefan Kleinwort (Gast)


Lesenswert?

neues Problem? lies die Antworten nochmal alle gena(er) durch!

Gruß, Stefan

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.