Forum: FPGA, VHDL & Co. UNIX timestamp erzeugen


von car (Gast)


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.

von D. I. (Gast)


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?

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

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

von car (Gast)


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.

von car (Gast)


Lesenswert?

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

von Viertelgott (Gast)


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

von car (Gast)


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

von Gast (Gast)


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

von Bo G. (boge-ro)


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

von Volker (Gast)


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

von Volker (Gast)


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()).

1
struct tm
2
{
3
  int tm_sec;         /* Seconds.   [0-60] (1 leap second) */
4
  int tm_min;         /* Minutes.   [0-59] */
5
  int tm_hour;         /* Hours.   [0-23] */
6
  int tm_mday;         /* Day.      [1-31] */
7
  int tm_mon;         /* Month.   [0-11] */
8
  int tm_year;         /* Year   - 1900.  */
9
  int tm_wday;         /* Day of week.   [0-6] Sunday == 0 */
10
  int tm_yday;         /* Days in year.[0-365]   */
11
  int tm_isdst;         /* DST.      [-1/0/1]*/
12
}; 
13
14
const unsigned short int __mon_yday[2][13] =
15
  {
16
    /* Normal years.  */
17
    { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 },
18
    /* Leap years.  */
19
    { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 } };
20
21
#define   SECS_PER_HOUR   (60L * 60L)
22
#define   SECS_PER_DAY   (SECS_PER_HOUR * 24L)
23
24
/* Compute the `struct tm' representation of *T,
25
   offset OFFSET seconds east of UTC,
26
   and store year, yday, mon, mday, wday, hour, min, sec into *TP.
27
   Return nonzero if successful.  */
28
int __offtime (t, offset, tp)
29
     unsigned long int *t;
30
     long int offset;
31
     struct tm *tp;
32
{
33
  long int days, rem, y;
34
  const unsigned short int *ip;
35
36
  days = *t / SECS_PER_DAY;
37
  rem = *t % SECS_PER_DAY;
38
  rem += offset;
39
  while (rem < 0)
40
    {
41
      rem += SECS_PER_DAY;
42
      --days;
43
    }
44
  while (rem >= SECS_PER_DAY)
45
    {
46
      rem -= SECS_PER_DAY;
47
      ++days;
48
    }
49
  tp->tm_hour = rem / SECS_PER_HOUR;
50
  rem %= SECS_PER_HOUR;
51
  tp->tm_min = rem / 60;
52
  tp->tm_sec = rem % 60;
53
  /* January 1, 1970 was a Thursday.  */
54
  tp->tm_wday = (4 + days) % 7;
55
  if (tp->tm_wday < 0)
56
    tp->tm_wday += 7;
57
  y = 1970;
58
59
#define __isleap(year) ((year) % 4 == 0 && ((year) % 100 != 0 || (year) % 400 == 0))
60
#define DIV(a, b) ((a) / (b) - ((a) % (b) < 0))
61
#define LEAPS_THRU_END_OF(y) (DIV (y, 4) - DIV (y, 100) + DIV (y, 400))
62
63
  while (days < 0 || days >= (__isleap (y) ? 366 : 365))
64
    {
65
      /* Guess a corrected year, assuming 365 days per year.  */
66
      long int yg = y + days / 365 - (days % 365 < 0);
67
68
      /* Adjust DAYS and Y to match the guessed year.  */
69
      days -= ((yg - y) * 365
70
          + LEAPS_THRU_END_OF (yg - 1)
71
          - LEAPS_THRU_END_OF (y - 1));
72
      y = yg;
73
    }
74
  tp->tm_year = y - 1900;
75
  if (tp->tm_year != y - 1900)
76
    {
77
      /* The year cannot be represented due to overflow.  */
78
      // __set_errno (EOVERFLOW);
79
      return 0;
80
    }
81
  tp->tm_yday = days;
82
  ip = __mon_yday[__isleap(y)];
83
  for (y = 11; days < (long int) ip[y]; --y)
84
    continue;
85
  days -= ip[y];
86
  tp->tm_mon = y;
87
  tp->tm_mday = days + 1;
88
  return 1;
89
}
90
91
92
/** Konvertiert gegliederte UTC-Angaben in Unix-Zeit.
93
 * Parameter und ihre Werte-Bereiche:
94
 * - jahr [1970..2106]
95
 * - monat [0..11]
96
 * - tag [1..31]
97
 * - stunde [0..23]
98
 * - minute [0..59]
99
 * - sekunde [0..59]
100
 */
101
unsigned long unixzeit(int jahr, int monat, int tag, 
102
              int stunde, int minute, int sekunde)
103
{
104
  const short tage_bis_monatsanfang[12] = /* ohne Schalttag */
105
    {0,31,59,90,120,151,181,212,243,273,304,334};
106
107
  unsigned long unix_zeit;
108
  unsigned long jahre=jahr-1970;
109
  int schaltjahre=((jahr-1)-1968)/4 - ((jahr-1)-1900)/100 + ((jahr-1)-1600)/400;
110
 
111
  unix_zeit=sekunde + 60L*minute + 60L*60L*stunde +
112
    (tage_bis_monatsanfang[monat]+tag-1L)*60L*60L*24L +
113
    (jahre*365L+schaltjahre)*60L*60L*24L;
114
 
115
  if ( (monat>2) && (jahr%4==0 && (jahr%100!=0 || jahr%400==0)) )
116
    unix_zeit+=60L*60L*24L; /* +Schalttag wenn jahr Schaltjahr ist */
117
 
118
  return unix_zeit;
119
}
120
121
int main( void ) {
122
  struct tm zeit_str;
123
  unsigned long int l, lt;
124
125
  l = 3261440000L; // Sekunden seit 1.1.70 00:00:00 für Montag, 08.05.2073, 03:33:20 Uhr
126
127
  if (!__offtime( &l, 0L, &zeit_str ) )
128
    return( 1 );  // Fehler
129
130
  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 );
131
132
  return(0);
133
}

von Klaus (Gast)


Lesenswert?

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

von Volker (Gast)


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

von Klaus (Gast)


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...

von Volker U. (volkeru)


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

von Christian B. (casandro)


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.

von Volker U. (volkeru)


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

von Volker U. (volkeru)


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:
1
   if ( (monat>1) && (jahr%4==0 && (jahr%100!=0 || jahr%400==0)) )
2
     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

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.