Forum: Mikrocontroller und Digitale Elektronik Uhrzeit aus RTC auf LCD ausgeben


von Christian O. (hightec)


Lesenswert?

Guten Abend,

Ich sitze nun seit ein paar Stunden an einem Problem und ich finde auch 
im Netz keine Hilfe die ich entweder verstehe oder die mir wirklich 
weiter hilft.

Und zwar habe ich eine kleine Schaltung aufgebaut die einen Atmega8, ein 
RTC-Modul (RV 8564-C2) und ein LCD Display (HD44780 Controller) 
beinhaltet.

Nun wollte ich ganz klassisch ersteinmal die Sekunden aus dem RTC-Modul 
auslesen (TWI) und auf dem LCD-Display ausgeben lassen.

Nun genau da hakt es schon.

Laut Datenblatt besteht das Byte des RTCs ,welches die Sekunden 
beinhaltet, aus dem oberstem Bit (Welches ausgelöscht werden muss, da es 
nur als Statusbit genutzt wird) und den 7 weiteren Bits worin die 
Sekunden im BCD-Format gespeichert sind.

Nun klappt es logischerweise nicht, den im TWDR ausgelesenen Wert ohne 
umweg auf das LCD-Display zu senden, da das Display die Zeichen ja im 
Ascii-Format braucht. (Nicht dass ich es nicht zumindest versucht hätte)
Die TWI/I2C verbindung scheint offentsichtlich zu funktioneren, da das 
auf dem LCD sichtbar werdende Kauderwelsch sich im Sekundentakt 
verändert.

Das heisst für mich ja soweit ich muss das oberste Bit löschen und das 
ausgelesene Byte danach in Ascii-Format umwandeln.

Nun habe ich schon so einiges gelesen wie "Rechne den Wert einfach + 48" 
bis hin zu ellenlangen Codes die die gewünschte Umwandlung vornehmen...
Nichts...

Ich habe es schon über kleine Umwege versucht, den BCD-Wert erst in 
Integer umzurechnen und dann mittels itoa() an das LCD zu senden... auch 
nichts...(liegt aber sehr wahrscheinlich an meinem fehlenden Wissen)

Das hier ist der Code den ich bisher habe und der (nach meiner Sicht) 
zumindest bis zum i2c_stop() funktioniert:
1
#include <avr/io.h>
2
#include <util/delay.h>
3
#include "lcd.h"
4
#include "twimaster.c"
5
#include <avr/interrupt.h>
6
int main (void)
7
{
8
9
  sei();
10
  TWCR = (1<<TWIE);
11
  lcd_init();
12
  i2c_init();
13
  while(1) {
14
  i2c_start(0b10100010);
15
  i2c_write(0x02);
16
  i2c_rep_start(0b10100011);
17
  i2c_readNak();
18
  i2c_stop();
19
20
  char Wert = TWDR;
21
  Wert = Wert & 0b01111111;
22
23
  lcd_string_xy(0,0,Buffer); //Das "Buffer" ist noch restbestand- nicht beachten.
24
  }
25
   
26
}

Erste kleine Frage vorweg:

Mache ich das mit folgendem Code richtig wenn ich das oberste Bit 
ausblenden möchte?
1
Wert = Wert & 0b01111111;

Zweite Frage:

Wie zum Teufel bekomm ich den aus dem RTC-Modul gelesenen Wert auf das 
LCD-Display?



Ich bin mir sicher, dass irgendjemand mir jetzt eine verbale Ohrfeige 
verpasst weil ich einen leichten weg nicht beachtet habe und komplett 
aus der Spur bin. Allerdings bin ich Anfänger in den Künsten der 
uC-Programmierung( in C# im allgemeinen auch) und bastel mir ein kleines 
Projekt damit ich das ganze besser lernen kann. ( Ich brauche ein Ziel 
um die Motivation des lernens zu halten ;-) )

Ich danke schonmal für die möglichen guten Antworten ;-)

Gruß

Christian

von Robert W. (robwa)


Lesenswert?

Hallo,

Christian O. schrieb:
> Mache ich das mit folgendem Code richtig wenn ich das oberste Bit
> ausblenden möchte?
> Wert = Wert & 0b01111111;

Das stimmt schon so, allerdings ist der Rest immer noch in BCD-Codiert.

Wenn die Sekunden BCD-codiert sind und sich in Wert das Sekundenbyte 
befindet, kannst Du das folgendermaßen auflösen:

[c]
  unsigned char SekundenString[3];

  SekundenString[0] = ((Wert & 0x70) >> 4) + 0x30;
  // Zehner-Stelle auswerten
  // (Wert & 0x70) wertet die Bits 6, 5 und 4 aus (und blendet Bit-7 
aus)
  // >> 4 schiebt die Bits auf die Pos. 2, 1 und 0
  // + 0x30 gibt den Offset für ein Zeichen im ASCII-Format dazu
  SekundenString[1] = (Wert & 0x0F) + 0x30;
  // Einer-Stelle auswerten
  SekundenString[2] = 0x00; // String-Ende
[\c]

HTH, Robert.

von Christian O. (hightec)


Lesenswert?

Robert W. schrieb:
> Das stimmt schon so, allerdings ist der Rest immer noch in BCD-Codiert.
>
> Wenn die Sekunden BCD-codiert sind und sich in Wert das Sekundenbyte
> befindet, kannst Du das folgendermaßen auflösen:
>
> [c]
>   unsigned char SekundenString[3];
>
>   SekundenString[0] = ((Wert & 0x70) >> 4) + 0x30;
>   // Zehner-Stelle auswerten
>   // (Wert & 0x70) wertet die Bits 6, 5 und 4 aus (und blendet Bit-7
> aus)
>   // >> 4 schiebt die Bits auf die Pos. 2, 1 und 0
>   // + 0x30 gibt den Offset für ein Zeichen im ASCII-Format dazu
>   SekundenString[1] = (Wert & 0x0F) + 0x30;
>   // Einer-Stelle auswerten
>   SekundenString[2] = 0x00; // String-Ende
> [\c]
>
> HTH, Robert.

Oh gott ja.. jetzt sehe ich auch woran mein Weiterkommen gescheitert 
ist. Ich bin die ganze zeit davon ausgegangen dass die einer und zehner 
stellen zusammen in allen 7 bits sind. Also 0101001 --> 41... das ist 
aber ja nicht BCD...

Ach Mist 3 Stunden um alle falschen Ecken gedacht.

Ich werde heute Abend dann versuchen diesen Lösungsansatz an mein 
Programm anzupassen und werde dann Bericht erstatten.

Eins ist klar.. Nachtschichten schlagen stark aufs Gemüt. ;-)

von spontan (Gast)


Lesenswert?

>0101001 --> 41..

Wenn die Binärzahl linksbündig ist (sind ja nur 7 bit), dann steht da 
nicht 41 sondern 29,

wenn das dann der BCDcode ist, so sind die Zehner eben 2, die Einer eben 
9.
Schau Dir doch nochmals die Codierung an. Binär, hex, bcd, dezimal was 
es sonst noch gibt.

von datasheet (Gast)


Lesenswert?

Und such Dir ein anderes Hobby, wenn Dir Nachtschichten aufs Gemüt 
schlagen ;-)

von Christian O. (hightec)


Lesenswert?

Nabend Leute,

vielen Dank für eure Hilfe, dass hat schonmal wunderbar geklappt.
Nun hab ich das nächste (aber etwas kleinere Problem).

Ich habe die ausgelesenen Sekunden nun so verarbeitet, dass ich sie 
bereits auf dem Display anzeigen und laufen lassen kann.

Nun wollte ich aber das Wort "Sekunden:" vorne anhängen. Also suchte ich 
nochmal nach der Funktion um Zeichenketten und Variablen in einem String 
zu verketten und habe mich erstmal mit strcat() veruscht:
1
unsigned char BCD10;
2
  unsigned char BCD1;
3
  char string;
4
  char stringsek1[20];
5
  char stringsek10[20];
6
  BCD10 = ((TWDR & 0x70) >> 4);
7
  BCD1  = (TWDR & 0x0F);
8
  string = "Sekunden:";
9
  itoa(BCD1,stringsek1,10);
10
  itoa(BCD10,stringsek10,10);
11
  strcat(string, stringsek10);
12
  strcat(string, stringsek1);
13
  lcd_string_xy(0,0,string);

Nun.. die Funktion verrichtet ihre Arbeit soweit das Zunächst auch 
"Sekunden: 46" auf dem Display steht (46 ist nur ein Beispiel).
Allerdings hängt diese Funktion nun in jedem Zyklus die neue Sekunde 
hinten dran. Sodass der String sich immer weiter verlängert.
"Sekunden: 46474849..".
Ich dachte mit der Zeile
1
string = "Sekunden:";

Gebe ich dem String wieder nur den Wert "Sekunden:"

Gruß

von Christian O. (hightec)


Lesenswert?

Der Vollständigkeithalber möchte ich noch hinzufügen dass der oben 
genannte Code sich in einer while-schleife befindet. Deswegen der immer 
wiederkehrende Zyklus.

Mein Ziel ist wie bestimmt zu erkennen ist, das ich das Datum und die 
Uhrzeit auf dem LCD wiedergeben möchte.

Das heisst dass ich irgendwie einen String zusammensetzen und ans LCD 
senden muss der wie folgt aussieht:

varWoTag "den" vatTag "." varMonat "." varJahr "  " varStunde ":" var 
Minute

(alles mit var sind die sich verändernden Werte und alles in " sind die 
festen Strings)

Gruß

: Bearbeitet durch User
von Robert W. (robwa)


Lesenswert?

Hallo,

zur Stringverarbeitung solltest Du folgenden Artikel lesen: 
http://www.mikrocontroller.net/articles/String-Verarbeitung_in_C
In Deiner initialisierung von "string" (char string;) fehlt die 
Arrayangabe -> char string[20];
Wenn Du den String wieder nur den Wert "Sekunden\0" geben willst, kannst 
Du die Funktion strcpy verwenden. Also statt:
    string = "Sekunden:";
einfach:
    strcpy(string, "Sekunden: ");
verwenden.

Übrigens die Initialisierungen der Variablen stringsek1 und stringsek10 
sind reichlich groß dimensioniert, diese sollten letztlich immer nur ein 
Zeichen und das Stringabschlußzeichen '\0' enthalten. Hier sollte der 
Wert [2] statt [20] ausreichen:

  char stringsek1[2];
  char stringsek10[2];

BG,
Robert.

von Peter D. (peda)


Lesenswert?

Christian O. schrieb:
> Das heisst dass ich irgendwie einen String zusammensetzen und ans LCD
> senden muss der wie folgt aussieht:
>
> varWoTag "den" vatTag "." varMonat "." varJahr "  " varStunde ":" var
> Minute

sprintf ist dein Freund.

von Karl H. (kbuchegg)


Lesenswert?

Christian O. schrieb:

> Nun.. die Funktion verrichtet ihre Arbeit


wobei das so ziemlich die komplzierste Variante ist, die ich mir 
vorstellen kann.

Wenn du sprintf, wie von PeDa vorgeschlagen benutzen kannst, dann zb so
1
  unsigned char BCD;
2
  char string[20];
3
4
  BCD = ((TWDR & 0x70) >> 4) + (TWDR & 0x0F);
5
  sprintf( string, "Sekunden: %02u", BCD );
6
  lcd_string_xy(0, 0, string);

Allerdings erhebt sich auch die Frage, wozu man mit dem Text 'Sekunden' 
rummachen muss. AUf dem LCD sieht sowieso kein Mensch, ob du jetzt nur 
eine einzige Ausgabe machst, oder deren 2.
1
  unsigned char BCD;
2
  char string[3];
3
4
  BCD = ((TWDR & 0x70) >> 4) + (TWDR & 0x0F);
5
  sprintf( string, "%02u", BCD );
6
  lcd_string_xy(  0, 0, "Sekunden: ");
7
  lcd_string_xy( 10, 0, string);
was sich in einem verringerten Speicherverbrauch für den temporären 
String niederschlägt.
Ist man soweit dann kann man sich fragen, wozu man jetzt eigentlich den 
sprintf noch braucht. denn irgendwie ist das etwas witzlos, zuerst die 
beiden BCD Ziffern zu einer Zahl zusammenzusetzen, nur damit sprintf die 
dann wieder auseinanderdröselt. Das eine ist die Umkehrung vom anderen. 
Und aus einer einzelnen Ziffer das entsprechende Zeichen zu machen, ist 
aber eine leichte Übung.

An dieser Stelle wird man sich überlegen, dass man eine Funktion 
bräuchte, die keinen kompletten String ausgibt, sondern nur ein 
einzelnes Zeichen.
Die Funktion wirst du wohl vorrätig haben, dann diese Funktionalität 
wird ja von lcd_string_xy auch gebraucht. Tatsächlich ist ja die Ausgabe 
eines Strings nichts anderes als in einer Schleife jedes einzelne 
Zeichen des Strings auszugeben.

Dann vereinfacht sich der Code zu
1
  lcd_string_xy(  0, 0, "Sekunden: ");
2
3
  lcd_char( ((TWDR & 0x70) >> 4) + '0' );
4
  lcd_char( (TWDR & 0x0F) + '0' );

Wenn man möchte, könte man sich die Bitoperationen noch ein wenig 
'schöner' gestalten, indem man sie in einem Makro versteckt.
1
#define BCD_HIGH(x)   ( ((x) & 0x70) >> 4 )
2
#define BCD_LOW(x)    ( (x) & 0x0F )
3
4
....
5
6
  lcd_string_xy(  0, 0, "Sekunden: ");
7
8
  lcd_char( BCD_HIGH( TWDR ) );
9
  lcd_char( BCD_LOW( TWDR ) );

und da kann man doch jetzt wirklich nicht mehr meckern. Das ist kurz, 
einfach, übersichtlich und verständlich.

: Bearbeitet durch User
von Joachim B. (jar)


Lesenswert?

Karl Heinz schrieb:
> Dann vereinfacht sich der Code zu  lcd_string_xy(  0, 0, "Sekunden: ");
>
>   lcd_char( ((TWDR & 0x70) >> 4) + '0' );
>   lcd_char( (TWDR & 0x0F) + '0' );
>
> Wenn man möchte, könte man sich die Bitoperationen noch ein wenig
> 'schöner' gestalten, indem man sie in einem Makro versteckt.#define
> BCD_HIGH(x)   ( ((x) & 0x70) >> 4 )
> #define BCD_LOW(x)    ( (x) & 0x0F )
>
> ....
>
>   lcd_string_xy(  0, 0, "Sekunden: ");
>
>   lcd_char( BCD_HIGH( TWDR ) );
>   lcd_char( BCD_LOW( TWDR ) );
>
> und da kann man doch jetzt wirklich nicht mehr meckern. Das ist kurz,
> einfach, übersichtlich und verständlich.

darüber habe ich auch die ganze Zeit gegrübelt ob man nicht Platz spren 
könnte wenn sprintf nicht mehr genutzt wird !

Dein Vorschlag "& 0x70) >> 4 sowie & 0x0F)" war ja auch schon in bcd2int 
im Uhrprogramm der CT, ich wollte das fast wieder ausbuddeln!
Und dieses einfach mit char('0') zu addieren die logische Konsequenz um 
die sprintf eben nicht mehr zu nutzen.

Aber keine Ahnung was das im MC spart im m1284p bis m2560 ist mir der 
Platz nie eng geworden, im m328p schon eher.

von Karl H. (kbuchegg)


Lesenswert?

Joachim B. schrieb:

> Aber keine Ahnung was das im MC spart im m1284p bis m2560 ist mir der
> Platz nie eng geworden, im m328p schon eher.

Mir gehst auch in erster Linie gar nicht so sehr um den verbrauchten 
Platz. Ich verwende in meinen Programmen auch sprintf, weil es eine 
einfache Sache ist und ich die Formatieroptionen ausnutzen kann.

Mir geht es vielmehr darum, dem TO zu zeigen, dass sein 
unübersichtlicher Codewust auch durch ein bischen Nachdenken und dem 
Nichtbestehen auf bestimmten Funktionen wie zb itoa, viel einfacher 
formuliert werden kann, was dann ja auch der Fehlervermeidung zu gute 
kommt.

Das andere ist das hier

> Mein Ziel ist wie bestimmt zu erkennen ist, das ich das Datum und
> die Uhrzeit auf dem LCD wiedergeben möchte.
> Das heisst dass ich irgendwie einen String zusammensetzen und
> ans LCD senden muss

Nein, das heisst es eben nicht. Selbst wenn da mehrere Informationen 
auszugeben sind, bedeutet das nicht, dass man das zuerst alles in einem 
einzigen String zusammenfassen muss. Ganz im Gegenteil wird man das 
nicht tun, sondern nur die Dinge am LCD updaten, die sich tatsächlich 
verändert haben. Das Datum verändert sich für 24 Stunden nicht. Daher 
gibt es überhaupt keinen Grund, das in jeder Sekunde wieder neu 
hinzuschreiben.
Versteift man sich aber darauf, zuerst im µC einen kompletten String 
zusammenzubauen, dann ist dieser Weg kontraproduktiv, weil er von der 
einfachen Lösung, einfach mit mehreren Ausgabeanweisungen die Dinge nach 
und nach (und mglw. nur bei Bedarf) auszugeben, ablenkt. Wobei natürlich 
dieses 'nach und nach' nur aus µC besteht. Für uns Menschen ist das 
immer noch viel zu schnell. Selbst 'nach und nach' klatscht der µC das 
alles einzeln so schnell hin, dass wir da nicht mehr mitkommen. Für uns 
erscheint 'die Ausgabe' auf einen Schlag, selbst dann wenn da mehrere 
Funktionsaufrufe involviert sind.

: Bearbeitet durch User
von Joachim B. (jar)


Lesenswert?

Karl Heinz schrieb:
> Versteift man sich aber darauf, zuerst im µC einen kompletten String
> zusammenzubauen, dann ist dieser Weg kontraproduktiv, weil er von der
> einfachen Lösung, einfach mit mehreren Ausgabeanweisungen die Dinge nach
> und nach (und mglw. nur bei Bedarf) auszugeben, ablenkt. Wobei natürlich
> dieses 'nach und nach' nur aus µC besteht. Für uns Menschen ist das
> immer noch viel zu schnell.

und trotzdem mache ich es auch so den string zusammenzubauen.

1. für die LCD Ausgabe
2. für die serielle Ausgabe

aber ich mache das nicht permanent im main loop sondern genau 3x pro 
Sekunde wenn ein LCD update fällig wird, Timer count 10ms bei 0, wird 
von 33 rückwärts gezählt if (lcd_update) lcd_update--;

im Main loop if (!lcd_update) {String basteln, ausgeben, lcd_update=33;}

klar reicht 1x pro Sekunde aber dann läuft eine Anzeige eventuell nicht 
so flüssig oder springt.

Mit LCD update alle 330ms finde ich einen brauchbaren Kompromiss.

: Bearbeitet durch User
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.