7-Segmentuhr mit 6 Digits ---------------------------------------------------------------------------------------------------- Nachdem ab und an nach einer Uhrenschaltung, einem Uhrenprogramm nachgefragt wird, wird hier eine Uhr mit 6 7-Segmentdigits vorgestellt, die somit fortlaufend auch die Sekunden anzeigt. Um aufzuzeigen, wie eine gemultiplexte Anzeige funktionieren kann (es geht sicherlich auch anderst, viele Wege fuehren nach Rom) wurde bewusst auf einen 7-Segmentanzeigetreibe wie bspw. einem TM1637 verzichtet. Hier wird dann der Aufwand wie beim Aufbau ersichtlich wurde dann deutlich groesser als es mit einem Anzeigetreiberbaustein der Fall waere. Die hier vorgestellte Uhr verfolgt nicht den "klassischen" Weg, in einem Timerinterrupt die Sekunden, Minuten und Stunden zu zaehlen, sie bedient sich der Quelldatei "my_time.c" die aehnlich der zu C gehoerenden Bibliothek "time.h" die Uhrzeit anhand der vergangenen Sekunden seit einem Referenz- datum vergangen sind (hier: 01.01.1970 00:00.00). Anderst als in "time.h" werden den Funktionen zur Datumsberechnung keine Zeiger als Argument uebergeben, sondern nur eine Struktur, die das Datum und die Uhrzeit beinhaltet. Die Anzeige der Uhr verwendet 7-Segment LED-Anzeigen mit gemeinsamer Anode. Zum Ansteuern der Anzeige wird die Quelldatei "mpx_6digit.c" verwendet. Die Uhr verfuegt ueber einen "Kalibriermodus", mit dem immer um 00:01.30 Uhr ein Ausgleich fuer die Sekundenungenauigkeit eines Tages erfolgen kann. Der Quarz des Autors taktet um ca. 69 ppm zu schnell und im Testaufbau wird hierbei beim Kalibrieren dann 6-Sekunden abgezogen. Somit laeuft die Uhr waehrend eines Tages um ca. +- 3 Sekunden falsch ! Die Anzahl der zu kalibrierenden Sekunden kann waehrend der laufenden Uhr ohne Neuuebersetzung des Programms eingegeben werden. In die Software koennte evtl. noch ein monatlicher Kalibriermodus fuer eine weitere und genauere Kalibrierung eingefuegt werden, hiervon wurde bei dieser Version jedoch abgesehen. Der Kalibrierwert selbst wird im internen EEProm gespeichert, so dass bei einem evtl. Strom- ausfall dieser Wert nicht neu eingegeben werden muss (der Kalibrierwert wird beim Start der Uhr automatisch geladen). Die Uhr selbst kann im Anzeigemodus eingestellt werden, je nachdem ob die Uhrzeit permanent oder im Wechsel mit dem Datum angezeigt werden soll. Ausserdem ist die Helligkeit der Anzeige einstellbar. ################################################################################################## Funktionsweise der Uhr ################################################################################################## Die Uhr wird mittels eines ATmega168 / 328p betrieben (getestet). Prinzipiell sollten auch ATmega48 und ATmega88 funktionieren (der compilierte Programmcode ist bei Verwendung von AVR-GCC Version 7.3.0 3950 Byte gross), allerdings sind diese beiden Controller ungetestet. Der Coretakt fuer den ATmega betraegt 8 MHz bei internem Takt. Die Fuses-Einstellungen hierfuer sind: ATmega168 : LoFuse = 0xE2, Hi-Fuse = 0xDF, Ex-Fuse = 0xF9 ATmega328p: LoFuse = 0xE2, Hi-Fuse = 0xD9, Ex-Fuse = 0xFF Das Uhrwerk selbst wird mit einem 32,768 kHz Uhrenquarz betrieben. Die Lastkapazitaeten fuer den Uhrenquarz sollten 10pF hierbei nicht ueberschreiten. Fuer den Betrieb mittels des Uhrenquarzes wird Timer2 fuer einen Compare-Match Interruptbetrieb konfiguriert, der den Interruptvektor jede halbe Sekunde aufruft. Innerhalb dieses Interrupts werden zu jeder zweiten halben Sekunde die globale 32-Bit Variable sekcount hochgezaehlt. Diese kann dann fuer eine weitere Bearbeitung / Ausgabe mittels den Funktionen in "my_time.c" ausgewertet werden. Desweiteren erfolgt in diesem Interruptvektor um 00:01.30 Uhr eines jeden Tags die Kalibrierung der Uhr. Die Uhrzeit und das Datumg der Uhr wird mittels 3 Tasten eingestellt. Die Anschlussbelegung der Tasten sind in der Defaulteinstellung: - PD5 fuer die Auswahltaste - PD6 fuer die Plustaste - PD7 fuer die Minustaste Die Deklaration der Tastenanschluesse erfolgt in der seperaten Datei "uhr_6igit_keys.h", damit das eigentliche Uhrenprogramm relativ uebersichtlich bleibt. Beim Start der Uhr wird eine Uhrzeit mittels Zeitstempel vorgegeben, hier 1577836800ul, was dem 01.01.2020 00:00.00 Uhr entspricht. Dieser Wert wird der Variable "sekcount" zugewiesen, die im Timerinterrupt kontinuierlich weiter gezaehlt wird. In einer Endlosschleife wird permanent dieser Sekundenzaehler in eine Struktur umgerechnet, die danach das Datum und die Uhrzeit enthaelt: mydate= mydate_get(sekcount); Je nach Anzeigemodus wird nur die Uhrzeit permanent oder im Wechsel mit dem Datum angezeigt. Wird in dieser Endlosschleife die Select-Taste gedrueckt, erscheint auf der Anzeige das "Uhrenmenu". Mit der Plus- oder Minustaste koennen einzelne Menupunkte sequentiell aktiviert werden: - SEt - Mod - CAL - HELL Nach Betaetigen der Select-Taste wird der entsprechende Uhrenmenupunkt ausgefuehrt. SEt --------------------------------------------------------------------------------------------- Das Datum der Uhr wird in der Reihenfolge: Jahr, Monat, Tag, Stunde, Minute, Sekunde eingestellt. Jede Einstellung muss mittels der Select-Taste bestaetigt werden Mod --------------------------------------------------------------------------------------------- Anzeigemodus der Uhr (Permanent- oder Wechselanzeige) Mode 0 : permanente Uhrzeitanzeige Mode 1 : wechselnde Anzeige von Uhrzeit und Dateum CAL --------------------------------------------------------------------------------------------- Angabe der Sekunden, die die Uhr an einem Tag falsch "laeuft". Zulaessige Werte sind hier +- 29 Sekunden HELL --------------------------------------------------------------------------------------------- Die Helligkeit der Anzeige 0 : sehr dunkel 15: maximale Helligkeit ################################################################################################## Beschreibung der wichtigsten Funktionen von "uhr_6digit_mpx.c" (Kopie des Kommentars des Quellprogramms) ################################################################################################## ----------------------------------------------------------- getint8fromkey ----------------------------------------------------------- liest einen 2-stelligen 8-Bit Zahlenwert ueber die Plus- Minus-Tasten ein. Ein laengeres Betaetigen einer Taste laesst entsprechend den Zaehlerwert "val" automatisch inkrementieren / dekrementieren. Uebergabe: entry : vorbelegter Zahlenwert min : kleinster Zahlenwert der eingegeben werden kann max : groesster Zahlenwert der eingegeben werden kann pm : plusminus 1 => zeigt bei negativen Werten das Minus- zeichen auf Digit 2 an, ansonsten wird Digit 2 geloescht 0 => keine Vorzeichenangabe Rueckgabe: eingegebener Zahlenwert ----------------------------------------------------------- eep_write ----------------------------------------------------------- schreibt einen signed 8-Bit Wert in das interne EEPROM Uebergabe: addr: Speicheradresse des zu schreibenden Bytes data: zu speichernder Wert ----------------------------------------------------------- eep_read ----------------------------------------------------------- liest einen signed 8-Bit Wert aus dem internen EEPROM Uebergabe: addr: Adresse des Bytes im EEPROM Rueckgabe: gespeicherter Wert ----------------------------------------------------------- tx2fb ----------------------------------------------------------- kopiert 4 Buchstaben in den Framebuffer auf die linken 4 Digits Uebergabe: *buf: Zeiger auf ein 4 Byte grosses Array, das die Bitmaps fuer den Framebuffer enthaelt ################################################################################################## Funktionsweise des Multiplexens innerhalb der Datei "mpx_6digit.c" ################################################################################################## Innerhalb des zu der Datei gehoerende Header-Files "mpx_6digit.h" werden die Anschluesse, die beim Multiplexen angesteuert werden deklariert. Hierbei werden die Digittreiber mit kseg5 (linke Stelle) bis kseg0 (rechte Stelle) sowie die entsprechenden Segmente eines Digits, seg_a bis seg_g und seg_dp (fuer den Dezimalpunkt) deklariert. Innerhalb der .c Quelle wird ein Framebuffer (Array mit 6 Byteelemente) definiert, welches die Bitmaps aufnimmt, die waehrend des Betriebs angezeigt werden. Desweiteren ein globales Array "dig_pwm", welches die Helligkeitswerte bei der Anzeige aufnimmt. Prinzipiell ist jedes einzelne Digit in der Helligkeit veraenderbar, hiervon wird jedoch kein Gebrauch gemacht. Das Array "seg7_bmp" enhaelt die Bitmaps, welche die entsprechenden Ziffern auf der 7-Segment- anzeige darstellen. Zum Multiplexen der Anzeige wird Timer1 verwendet, der den Interruptvektor Timer1 CompA ansteuert. Innerhalb dieses Vektors geschieht die Zuordnung des Framebuffers zu den einzelnen anzuzeigenden Digits sowie die Helligkeitseinstellung der Digits. Nach dem Initialisieren der Pins fuer die zu multiplexenden Anschluesse und des Timers 1 geschieht eine Anzeige nun automatisch. Im Hauptprogramm muss nun noch lediglich dem Framepuffer ent- sprechende Bitmaps zugewiesen werden. Fuer die Zuordnung einer 2-stelligen Zahl (wie hier bei der Uhr) zu den entsprechenden Bitmaps sowie deren Anzeige steht die Funktion: void fb_setdez(uint16_t val, uint8_t dpos); Zur Verfuegung. fb_setzdez beschreibt den Framebuffer mit einem 2 stelligen Dezimalwert ab der Position dpos. Fuer dpos== 0 gilt: Die rechte Anzeigeposition wird ausgewaehlt. Beispiele: fb_setdez(23,0); // laesst auf der Sekundenposition die Zahl 23 erscheinen. fb_setdez(12,2); // laesst auf der Minutenposition die Zahl 12 erscheinen. ################################################################################################## Funktionsweise der Quelldatei "my_time.c" ################################################################################################## "my_time" ist eine Softwarebibliothek, die wie die Standardbibliothek "time.h" aus einem Zeit- stempel ein dazugehoerendes Datum oder aus einem Datum einen Zeitstempel berechnen kann. Der Einfachkeit halber wird im Gegensatz zu time.h NICHT mit Zeigern gearbeitet (was einen etwas hoeheren RAM-Speicherbedarf zur Folge hat). Der Zeitstempel hier ist eine einfache 32-Bit Integervariable. Diese Variable berechnet fuer ein bestimmtes Datum, wieviele Sekunden seit dem 01.01.1970 00:00.00 vergangen sind (sofern das define < yearofs > nicht veraendert wird. Hieraus ergeben sich gueltige Daten (Daten ist der Plural von Datum): 01.01.1970 00:00.0 bis 06.02.2106 06:28.15 "my_time" wurde geschrieben, um auf einfache Weise mit einem Datum bspw. fuer einen Datenlogger oder eine Zeitschaltuhr rechnen zu koennen. Das Zaehlen von Sekunden und aufaddieren zu Minuten, Stunden usw. wird hiermit hinfaellig, es muss nur noch zu einem Zeitstempel die entsprechenden Sekunden hinzuaddiert oder subtrahiert werden. Um bspw. die hier vorgestellte Uhr zu programmieren muss lediglich in einem Interruptvektor, der jede Sekunde aufgerufen wird, der Zeitstempel inkrementiert werden. "my_time" deklariert eine Struktur, "mydate_time" deren "Mitglieder" (member) year, month, day, hour, min, sec sind. Hiervon wird ein Variablentyp < mydate_time > abgeleitet. Um mit "my_time" arbeiten zu koennen ist es dann lediglich nur noch noetig, eine Variable diesen Typs zu definieren (Beispiel): mydate_time mydate; Das Beschreiben der Struktur geschieht hier wie mit jeder anderen Struktur auch: mydate.year= 2024; mydate.month= 7: mydate.day= 1; mydate.hour= 19; mydate.min= 34; mydate.sec= 33; Um aus dieser Struktur einen Zeitstempel zu generieren bedarf es des Aufrufes von mydate_getstamp(mydate_time mydate); Bsp.: uint32_t zeitstempel; zeitstempel= mydate_getstamp(mydate); Die Variable zeitstempel enthaelt nun die Anzahl Sekunden, die seit dem 01.01.1970 vergangen sind, fuer das Datum 01.07.2024 | 19:34.33 entspricht dieses 1719862473. Um aus einem gegebenen Zeitstempel ein Datum zu generieren muss die Funktion mydate_get(uint32_t stamp) aufgerufen werden. Bsp.: uint32_t zeitstempel, zeitstempel2; my_datetime mydate, mydate2; mydate.year= 2024; mydate.month= 7: mydate.day= 1; mydate.hour= 19; mydate.min= 34; mydate.sec= 33; zeitstempel = mydate_getstamp(mydate); zeitstempel2 = zeitstempel + 3600; mydate2= mydate_get(zeitstempel2); // mydate2 beinhaltet nun ein Datum eine Stunde spaeter als in mydate. Ein Wochentag kann mittels mydate_getwtag(mydate_time mydate) berechnet werden: Bsp.: char wochentag; wochentag= mydate_getwtag(mydate); Der Rueckgabewert von < mydate_getwtag > beginnt mit 0 und dieses repraesentiert den Tag Sonntag. Der 01.07.2024 war ein Montag und demzufolge wird beim obigen Aufruf der Wert 1 zurueck geliefert. ------------------------------------------------------------------------ Zusammenfassung der Funktionsuebersicht von my_time.c ------------------------------------------------------------------------ ------------------------------------------------------------------------ mydate_time mydate_get(uint32_t stamp); ------------------------------------------------------------------------ Ermittelt aus einem Zeitstempel (Sekunden die seit dem 01.01.1970 00:00.00 vergangen sind) das Datum. Uebergabe: stamp : Anzahl Sekunden seit dem 01.01.1970 Rueckgabe: Struktur, in der year, month, day, hour, min und sec abgebildet werdenn. ------------------------------------------------------------------------ uint32_t mydate_getstamp(mydate_time mydate); ------------------------------------------------------------------------ Ermittelt aus einem Kalenderdatum der Struktur mydate den Zeitstempel (vergangene Sekunden seit dem 01.01.1970 00:00.0) Uebergabe: mydate: Struktur fuer year, month, day, hour, min, sec Rueckgabe: Zeitstempel in Sekunden ------------------------------------------------------------------------ char mydate_getwtag(mydate_time mydate); ------------------------------------------------------------------------ Berechnet zu einem bestimmten Datum der Struktur mydate den Wochentag (Algorithmus nach Carl Friedrich Gauss): Uebergabe: mydate: Struktur fuer year, month, day, hour, min, sec Rueckgabe: der Tag der Woche, beginnend ab Sonntag. Sonntag entspricht 0. Bsp.: 01.07.2024 06:03.08 Rueckgabewert ist hier 1 und somit entspricht diese Datum dem Kalendertag Montag.