mikrocontroller.net

Forum: FPGA, VHDL & Co. UNIX timestamp erzeugen


Autor: car (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo Leute
Ich muss für ein VHDL Projekt ein Zeitsignal, welches ich über GPS 
empfange in einen UNIX timestamp umwandeln. Der UNIX timestamp ist die 
Anzahl Sekunden seit dem 01.01.1970 00:00 Uhr. Das GPS Signal enthält 
aber normale Werte für Datum und Zeit.
Hat da vielleicht jemand einen VHDL Code der diese Umwandlung macht? Ich 
stell mir das nicht sehr einfach vor wegen den Schaltjahren und 
Schaltsekunden die zu berücksichtigen sind.

Danke für eure Hilfe.

Autor: D. I. (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Wie wärs wenn du dir den Algorithmus raussuchst wie UNIX das macht bzw. 
wie die entsprechenden C-Funktionen aussehen und das in VHDL ummünzt?

Autor: Lothar Miller (lkmiller) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
> Zeitsignal ... in einen UNIX timestamp umwandeln.
Nur zur Simulation oder soll das auf Hardware laufen?

Autor: car (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Nein, es sollte schon auf der Hardware laufen.

@Christopher: Daran habe ich auch schon gedacht, jedoch wird die 
Implementation in C einfacher sein, denke ich. Am idealsten wäre es halt 
schon wenn das schon jemand hätte.

Autor: car (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Sorry, ich habe nicht erwähnt auf welcher Hardware das laufen sollte: 
Virtex-4 FPGA

Autor: Viertelgott (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Virtex4 kann kein GPS.

Du solltest dir erstmal einen passenden GPS-Empfänger raussuchen und 
schauen wie der angesteuert wird (wohl I2C). Dann suchst du dir ein 
passendes I2C-VHDL Modul und pappst einen pico oder microblaze 
(uC-Softcore) dran und schreibst das Programm zur Umwandlung in C.

MfG

Autor: car (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo Viertelgott
Danke für die Antwort.
Das GPS Signal liegt in Form eines seriellen Datenstreams vor. (NMEA 
Protokoll). Darin ist die UTC Zeit codiert. Es gelingt mir diese zu 
decodieren. Die Umwandlung in einen Unix Timestamp möchte ich eben in 
VHDL machen.
Mittlerweile ist es mir gelungen mit Matlab einen Algorithmus zu 
schreiben, der diese Umwandlung macht. Ich denke ich kann diesen 
Algorithmus mit einer Statemachine nachbauen.

Gruss

Autor: Gast (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hey Car,
mich würde der Algorithmus interessieren! Gibt es da Besonderheiten oder 
muss man nur die Schalttage berücksichtigen…  Welchen GPS-Empfänger 
möchtest du verwenden?
Gruß
Gast

Autor: Bo Ge (boge-ro)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo Car,

das Thema ist zwar schon ein gutes Jahr alt, aber auch mich würde der 
Algorithmus interessieren.
Ich möchte es auf einem ATMega umsetzen.

Hab zwar als Ausgangsbasis den http://de.wikipedia.org/wiki/Unixzeit 
genommen, allerdings bin ich der Meinung, der stimmt nicht 100%ig.

Ich habe den Code nach Bascom portiert und habe bei Daten nach dem 
31.12.2004 fehlerhafte Timestamps im Folgejahr nach einem Schaltjahr.

Dort bin ich zwar stets um einen Tag falsch, was sich mit einer extra 
Abfrage ja kompensieren ließe - allerdings macht mich dies skeptisch.

Ich wäre dir also dankbar, wenn ich deinen Algorithmus bekommen könnte.


Gruß


BoGe-Ro

Autor: Volker (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Auch mich würde eine Lösung hierfür sehr interessieren! Die Quellcodes 
von gcc für mktime() sind extrem aufwändig und erzeugen eine gewaltige 
Codemenge, die z.B. in einem AVR-Controller nicht akzeptabel wäre.

Ich nutze einen ATTiny85 zum Empfang eines DCF-Signals. Auch hier liegt 
die Zeit nur als "struct tm" vor, also aufgedröselt in Jahr, Monat, Tag, 
Stunde, Minute, Sekunde. Bisher wandern die Daten als solcher Bytestream 
per I2C nach draußen. Ich will aus Kompatibilitätsgründen aber auch 
gerne noch einen Unix Timestamp (time_t) mitliefern. Leider ist nix 
brauchbares zur Konvertierung dafür zu finden. Die Routine ist - wie 
schon gesagt - vermutlich auch nicht ganz trivial.

Der umgekehrte Weg (time_t -> struct tm) ist sehr einfach und in etwas 
über 1 kb zu codieren (liegt als Quelle in glibc-x.x/time/offtime.c 
vor). Eigentlich müsste doch (struct tm -> time_t) dann auch 
einigermaßen knapp zu codieren sein, also quasi "offtime.c rückwärts" 
;-).

Denkbar wäre noch eine Approximation, indem man den vermuteten time_t 
Wert näherungsweise bestimmt, diesen in eine struct tm umrechnet und 
dann so lange Sekunden addiert, bis der "Probierwert" mit dem realen 
übereinstimmt. Das ist aber keine schöne und auch sehr rechenaufwändige 
Lösung.

Besser wäre eine vernünftige, nicht approximative Lösung. :-/

Tschö, Volker

Autor: Volker (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Sodele, hier nun Beispielcode für die Wandlung einer Zeit-Struktur tm 
(Jahr, Monat, Tag, Stunde, Minute, Sekunde) in einen Unix Timestamp 
(time_t / Sekunden seit 1.1.1970 00:00:00 Uhr) und wieder zürück.

Ich habe mich mal etwas mit dem Beispielprogramm bei Wikipedia 
beschäftigt. Es funktioniert so weit, aber es ist noch ein Fehler drin. 
Der Monat muss nicht von 1 (Januar) bis 12 (Dezember) eingegeben werden, 
sondern natürlich im Unix "struct tm"-Format: Januar = 0, Dezember = 
11!!! Dann funktioniert die Routine auch.

Außerdem habe ich durch Verwendung von unsigned longs die 
unixzeit-Funktion so abgeändert, dass Daten bis zum Jahr 2106 (time_t 
0xFFFFFFFF) verarbeitet werden können und nicht nur bis 2038, wie bei 
vorzeichenbehafteten 32 bit Werten. Aber ACHTUNG: Daten vor dem 
1.1.1970 dürfen dann mit dieser Funktion nicht verarbeitet werden und 
führen zu falschen Ergebnissen!

Im Folgenden mal ein auf den meisten AVR lauffähiges Programm, das mit 
WinAVR compiliert werden kann. In der Funktion main() wird zuerst aus 
einem time_t Wert (l) ein Datum als "struct tm" erzeugt (mit der 
Funktion __offtime()). Die Daten dieser Struktur werden danach wieder 
zurück in einen time_t Wert (lt) gewandelt (mit der Funktion 
unixzeit()).

struct tm
{
  int tm_sec;         /* Seconds.   [0-60] (1 leap second) */
  int tm_min;         /* Minutes.   [0-59] */
  int tm_hour;         /* Hours.   [0-23] */
  int tm_mday;         /* Day.      [1-31] */
  int tm_mon;         /* Month.   [0-11] */
  int tm_year;         /* Year   - 1900.  */
  int tm_wday;         /* Day of week.   [0-6] Sunday == 0 */
  int tm_yday;         /* Days in year.[0-365]   */
  int tm_isdst;         /* DST.      [-1/0/1]*/
}; 

const unsigned short int __mon_yday[2][13] =
  {
    /* Normal years.  */
    { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 },
    /* Leap years.  */
    { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 } };

#define   SECS_PER_HOUR   (60L * 60L)
#define   SECS_PER_DAY   (SECS_PER_HOUR * 24L)

/* Compute the `struct tm' representation of *T,
   offset OFFSET seconds east of UTC,
   and store year, yday, mon, mday, wday, hour, min, sec into *TP.
   Return nonzero if successful.  */
int __offtime (t, offset, tp)
     unsigned long int *t;
     long int offset;
     struct tm *tp;
{
  long int days, rem, y;
  const unsigned short int *ip;

  days = *t / SECS_PER_DAY;
  rem = *t % SECS_PER_DAY;
  rem += offset;
  while (rem < 0)
    {
      rem += SECS_PER_DAY;
      --days;
    }
  while (rem >= SECS_PER_DAY)
    {
      rem -= SECS_PER_DAY;
      ++days;
    }
  tp->tm_hour = rem / SECS_PER_HOUR;
  rem %= SECS_PER_HOUR;
  tp->tm_min = rem / 60;
  tp->tm_sec = rem % 60;
  /* January 1, 1970 was a Thursday.  */
  tp->tm_wday = (4 + days) % 7;
  if (tp->tm_wday < 0)
    tp->tm_wday += 7;
  y = 1970;

#define __isleap(year) ((year) % 4 == 0 && ((year) % 100 != 0 || (year) % 400 == 0))
#define DIV(a, b) ((a) / (b) - ((a) % (b) < 0))
#define LEAPS_THRU_END_OF(y) (DIV (y, 4) - DIV (y, 100) + DIV (y, 400))

  while (days < 0 || days >= (__isleap (y) ? 366 : 365))
    {
      /* Guess a corrected year, assuming 365 days per year.  */
      long int yg = y + days / 365 - (days % 365 < 0);

      /* Adjust DAYS and Y to match the guessed year.  */
      days -= ((yg - y) * 365
          + LEAPS_THRU_END_OF (yg - 1)
          - LEAPS_THRU_END_OF (y - 1));
      y = yg;
    }
  tp->tm_year = y - 1900;
  if (tp->tm_year != y - 1900)
    {
      /* The year cannot be represented due to overflow.  */
      // __set_errno (EOVERFLOW);
      return 0;
    }
  tp->tm_yday = days;
  ip = __mon_yday[__isleap(y)];
  for (y = 11; days < (long int) ip[y]; --y)
    continue;
  days -= ip[y];
  tp->tm_mon = y;
  tp->tm_mday = days + 1;
  return 1;
}


/** Konvertiert gegliederte UTC-Angaben in Unix-Zeit.
 * Parameter und ihre Werte-Bereiche:
 * - jahr [1970..2106]
 * - monat [0..11]
 * - tag [1..31]
 * - stunde [0..23]
 * - minute [0..59]
 * - sekunde [0..59]
 */
unsigned long unixzeit(int jahr, int monat, int tag, 
              int stunde, int minute, int sekunde)
{
  const short tage_bis_monatsanfang[12] = /* ohne Schalttag */
    {0,31,59,90,120,151,181,212,243,273,304,334};

  unsigned long unix_zeit;
  unsigned long jahre=jahr-1970;
  int schaltjahre=((jahr-1)-1968)/4 - ((jahr-1)-1900)/100 + ((jahr-1)-1600)/400;
 
  unix_zeit=sekunde + 60L*minute + 60L*60L*stunde +
    (tage_bis_monatsanfang[monat]+tag-1L)*60L*60L*24L +
    (jahre*365L+schaltjahre)*60L*60L*24L;
 
  if ( (monat>2) && (jahr%4==0 && (jahr%100!=0 || jahr%400==0)) )
    unix_zeit+=60L*60L*24L; /* +Schalttag wenn jahr Schaltjahr ist */
 
  return unix_zeit;
}

int main( void ) {
  struct tm zeit_str;
  unsigned long int l, lt;

  l = 3261440000L; // Sekunden seit 1.1.70 00:00:00 für Montag, 08.05.2073, 03:33:20 Uhr

  if (!__offtime( &l, 0L, &zeit_str ) )
    return( 1 );  // Fehler

  lt = unixzeit( zeit_str.tm_year+1900, zeit_str.tm_mon, zeit_str.tm_mday, zeit_str.tm_hour, zeit_str.tm_min, zeit_str.tm_sec );

  return(0);
}

Autor: Klaus (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
komisch, mein VHDL-Compiler spuckt lauter Fehler aus... Oder ob ich das 
mit dem Verilog Compiler probieren muss?

Autor: Volker (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Klaus schrieb:
> komisch, mein VHDL-Compiler spuckt lauter Fehler aus... Oder ob ich das
> mit dem Verilog Compiler probieren muss?

VHDL hat mit ANSI C nichts zu tun, es ist eine eigene Sprache, die mehr 
Pascal ähnlich ist. Mein obiger Code ist für gcc gedacht (WinAVR bzw. 
Atmel AVR Studio). Du musst dir WinAVR installieren, wenn du das 
compilieren willst, oder es in VHDL umschreiben ;-).

http://winavr.sourceforge.net/

Gruß, Volker

Autor: Klaus (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Volker schrieb:
> VHDL hat mit ANSI C nichts zu tun, es ist eine eigene Sprache, die mehr
> Pascal ähnlich ist. Mein obiger Code ist für gcc gedacht (WinAVR bzw.
> Atmel AVR Studio). Du musst dir WinAVR installieren, wenn du das
> compilieren willst, oder es in VHDL umschreiben ;-).

Komisch, und ich dachte in diesem Thread geht es um VHDL...

Autor: Volker U. (volkeru)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Klaus schrieb:
> Volker schrieb:
>> VHDL hat mit ANSI C nichts zu tun, es ist eine eigene Sprache, die mehr
>> Pascal ähnlich ist. Mein obiger Code ist für gcc gedacht (WinAVR bzw.
>> Atmel AVR Studio). Du musst dir WinAVR installieren, wenn du das
>> compilieren willst, oder es in VHDL umschreiben ;-).
>
> Komisch, und ich dachte in diesem Thread geht es um VHDL...

UPS! Au weia, da habe ich mich im Thread geirrt :-). So ist das, wenn 
man die Suchfunktion benutzt und dann gar nicht so genau hinschaut. 
SORRY SORRY!!! :-)

Vielleicht schreibt das ja mal jemand in VHDL um?!

Nichts für ungut, Volker

Autor: Christian Berger (casandro) Flattr this
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Also VHDL ist eine Hardwarebeschreibungssprache, da kann man nicht so 
einfach Programmcode umschreiben.

Nehmen wir mal an, Du hast das NMEA Paket schon eingelesen, 
beispielsweise in Schieberegistern.

Dann könntest Du beispielsweise folgendes machen. Du machst 2 
Zeitzähler. Der eine zählt einfach 32 Bit durch (Unix-Zeit), der andere 
zählt in Sekunden, Minuten, Stunden, Tag, Monat, Jahr, wobei natürlich 
die Schaltjahre beachtet werden müssen.

Jetzt setzt Du beide auf Anfang und lässt sie so lange zählen, bis die 
UTC-Zeit übereinstimmt. Nach spätestens 4,3 Milliarden Taktzyklen hast 
Du das Ergebnis.

Nun ist das nicht besonders effizient. Was Du machen kannst ist dem 
Unix-Zähler Taktsignale zu geben, bei denen er 365 oder 366 Tage 
weiterzählt, sowie 1, 28, 29, 30 oder 31 Tage.

Jetzt kannst Du beide Zähler zuerst um die Jahre, dann die Monate, und 
dann die Tage inkrementieren. Das kannst Du auch auf Stunden, Minuten 
und Sekunden erweitern. Damit hast Du innerhalb von grob 100 Taktzyklen 
die Zeit umgerechnet.

Autor: Volker U. (volkeru)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Christian Berger schrieb:
> Also VHDL ist eine Hardwarebeschreibungssprache, da kann man nicht so
> einfach Programmcode umschreiben.

Das erinnert mich stark an die Steuerungstechnik in meinem Studium, wo 
wir SPS "programmiert" haben. Obwohl ich das kaum als Programmieren 
bezeichnen würde. Aber Variablen gabs da auch und einfache arithmetische 
Operatoren ebenso.

> Nehmen wir mal an, Du hast das NMEA Paket schon eingelesen,
> beispielsweise in Schieberegistern.
> [..]

Schön erklärt! Aber es dürfte schwierig sein, aus dem Unix-Zeitwert 
wieder den Datums- und Uhrzeitwert zurückzurechnen, oder? Allenfalls 
auch durch eine Approximation, so wie du es beschrieben hast...

Da bin ich schon froh, dass ich mich ausschließlich aufs "richtige" 
Programmieren verlegt habe und mit solchen Makro- oder 
Beschreibungssprachen wenig am Hut habe. Ich denke, der höhere 
Lernaufwand lohnt sich langfristig gesehen allemal. ;-)

Tschö, Volker

Autor: Volker U. (volkeru)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo,

wie mir grade aufgefallen ist, befindet sich leider noch ein Fehler in 
dem weiter oben von mir geposteten Code. Dieser tritt nur in 
Schaltjahren im März auf, weshalb ich ihn auch jetzt erst entdeckt habe. 
:-(

Im Code-Teil zur Konvertierung gegliederter UTC-Angaben in Unix-Zeit 
heißt es:

>   if ( (monat>2) && (jahr%4==0 && (jahr%100!=0 || jahr%400==0)) )
>     unix_zeit+=60L*60L*24L; /* +Schalttag wenn jahr Schaltjahr ist */

Es muss aber korrekt heißen:
   if ( (monat>1) && (jahr%4==0 && (jahr%100!=0 || jahr%400==0)) )
     unix_zeit+=60L*60L*24L; /* +Schalttag wenn jahr Schaltjahr ist */

Der Monat muss also auf größer 1 getestet werden und nicht auf größer 2! 
Sorry dafür!

Gruß, Volker

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [vhdl]VHDL-Code[/vhdl]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.