Forum: Mikrocontroller und Digitale Elektronik Auslesung von DS1307 RTC


von GC (Gast)


Lesenswert?

Guten Morgen,
hoffe mir kann jemand helfen.
Ich habe eine Basisplatine auf der ein ATMEGA128A mit 16MHz verbaut ist 
die mit der RTC DS1307 über I2C kommuniziert.
Eine zweite Platine, die über einen ATMEGA32A besitzt wird über SPI an 
die Basisplatine angeschlossen und soll das eingeleste Datum und Uhrzeit 
anzeigen.
Soweit so gut. Eine Anzeige der Uhrzeit etc bekomme ich hin nur habe ich 
das Problem, dass manchmal einfach Zahlen wegfallen.
Ich habe versucht mir den heutigen Tag ausgeben zu lassen und zwar 8415. 
Manchmal erscheint aber nur 840 dann wieder 8415 dann wieder 840 etc.
Dann habe ich versucht mir mal das alles mit 8:4:15 7:00:01 anzeigen zu 
lassen. Manschmal erscheint vorne eine null und alles wird nach links 
geschoben und somit verschwindet die 8.
(Hoffe ich konnte mein Problem einigermaßen erklären)
Nun ist die Frage ob es sein kann das ich beim auslesen der RTC müll 
bekomme oder ich bei der Konvertierung in einen String einen fehler 
mache oder sogar beim übertragen mich vertute.

Zum Quellcode:
1
#include "spi_master_slave.h"
2
3
volatile unsigned char data;
4
5
// SPI Transmission/reception complete ISR
6
ISR(SPI_STC_vect)
7
{  
8
  int i;
9
//  SWITCH_LED3;
10
  
11
  if (status == 0) // Befehl empfangen um Antwort zurück zu senden
12
  {
13
    data = SPDR;  // Ankommende Daten über SPI in Buffer Schreiben
14
  
15
    if (spi_data_pos < sizeof(spi_data_buf) )
16
    {
17
      spi_data_buf[spi_data_pos++] = data;  
18
      if (data == '\n')
19
      {
20
        spi_data_pos = 0;
21
        if (strcmp(spi_data_buf,"TIME\n") == 0)
22
        {
23
          //sprintf(spi_data_buf, "%s%d%s%d%s%d%s%d%s%d%s%d", ":", systemtime.datum.day, ":", systemtime.datum.month, ":", systemtime.datum.year, ":", systemtime.time.hour, ":", systemtime.time.minute, ":", systemtime.time.second);
24
          //strcpy(spi_data_buf,time_buf); 
25
          sprintf(spi_data_buf, "%d%d%d", systemtime.datum.day,systemtime.datum.month, systemtime.datum.year);
26
          status = 1;  // Nächste Übertragung wird Antwort gesendet  
27
        }
28
        if (strcmp(spi_data_buf,"TEMPA\n") == 0)
29
        {
30
          sprintf(spi_data_buf, "%s%d%s%d%s%d%s%d", "A:", terrariumsteuerung_a.TEMP_A, "B:", terrariumsteuerung_a.TEMP_B, "C:", terrariumsteuerung_a.TEMP_C, "D:", terrariumsteuerung_a.TEMP_D);
31
          status = 1;  // Nächste Übertragung wird Antwort gesendet  
32
        }
33
        if (strcmp(spi_data_buf,"LESEWERT_TERRARIUMA\n") == 0)
34
        {
35
          sprintf(spi_data_buf, "%d%s%d%s%d%s%d%s%d%s%d%s%d%s%d%s%d%s%d%s%d%s%d%s%d%s%d%s%d%s%d%s%d%s%d%s%d%s%d%s%d%s%d%s%d%s%d%s%d%s%d%s%d%s%d%s%d%s%d%s%d%s%d%s%d%s%d%s%d%s%d%s%d%s%d%s",   
36
                terrariumsteuerung_a.AUTO, ":",
37
                terrariumsteuerung_a.WINTERSCHLAFZEIT , ":",
38
                terrariumsteuerung_a.WINTERSCHLAF_TEMP , ":",
39
                terrariumsteuerung_a.TAGESLICHT_PROZENT , ":",
40
                terrariumsteuerung_a.DIMMEROUT_TAGESLICHT_ON , ":",
41
                terrariumsteuerung_a.DIMMEROUT_TEMPERATUR_A_PROZENT , ":",
42
                terrariumsteuerung_a.DIMMEROUT_TEMPERATUR_B_PROZENT , ":",
43
                terrariumsteuerung_a.MAX_TEMP_A , ":",
44
                terrariumsteuerung_a.MAX_TEMP_B , ":",
45
                terrariumsteuerung_a.MIN_TEMP_A , ":",
46
                terrariumsteuerung_a.MIN_TEMP_B , ":",
47
                terrariumsteuerung_a.TEMP_A , ":",
48
                terrariumsteuerung_a.TEMP_B , ":",
49
                terrariumsteuerung_a.TEMP_C , ":",
50
                terrariumsteuerung_a.TEMP_D , ":",
51
                terrariumsteuerung_a.ontime.hour , ":",
52
                terrariumsteuerung_a.ontime.minute , ":",
53
                terrariumsteuerung_a.ontime.second , ":",
54
                terrariumsteuerung_a.offtime.hour , ":",
55
                terrariumsteuerung_a.offtime.minute , ":",
56
                terrariumsteuerung_a.offtime.second , ":",
57
                terrariumsteuerung_a.sonnenaufgang[0] , ":",
58
                terrariumsteuerung_a.sonnenaufgang[1] , ":",
59
                terrariumsteuerung_a.sonnenaufgang[2] , ":",
60
                terrariumsteuerung_a.sonnenaufgang[3] , ":",
61
                terrariumsteuerung_a.sonnenaufgang[4] , ":",
62
                terrariumsteuerung_a.sonnenaufgang[5] , ":",
63
                terrariumsteuerung_a.sonnenaufgang[6] , ":",
64
                terrariumsteuerung_a.sonnenaufgang[7] , ":",
65
                terrariumsteuerung_a.sonnenaufgang[8] , ":",
66
                terrariumsteuerung_a.sonnenaufgang[9] , ":",
67
                terrariumsteuerung_a.sonnenuntergang[0] , ":",
68
                terrariumsteuerung_a.sonnenuntergang[1] , ":",
69
                terrariumsteuerung_a.sonnenuntergang[2] , ":",
70
                terrariumsteuerung_a.sonnenuntergang[3] , ":",
71
                terrariumsteuerung_a.sonnenuntergang[4] , ":",
72
                terrariumsteuerung_a.sonnenuntergang[5] , ":",
73
                terrariumsteuerung_a.sonnenuntergang[6] , ":",
74
                terrariumsteuerung_a.sonnenuntergang[7] , ":",
75
                terrariumsteuerung_a.sonnenuntergang[8] , ":",
76
                terrariumsteuerung_a.sonnenuntergang[9] , ":",
77
                terrariumsteuerung_a.FEUCHTIGKEIT , ":"
78
                );
79
          status = 1;  // Nächste Übertragung wird Antwort gesendet  
80
        }
81
      }
82
    }
83
  }
84
  else if (status == 1) // Befehl empfangen um Antwort zurück zu senden
85
  {
86
    data = SPDR;  // Ankommende Daten über SPI in Buffer Schreiben
87
    
88
    SPDR = spi_data_buf[spi_data_pos];  // Antwort senden
89
    spi_data_pos++;
90
    if (data == '\n')
91
    {
92
      for(i=0; i<spi_data_pos; i++)  // Datenspeicher löschen
93
      {
94
        spi_data_buf[i] = 0x00;
95
      }
96
      status = 0;
97
      spi_data_pos = 0;
98
    }
99
  }
100
}

spi_master_slave.h
1
/*
2
 * spi_master_slave.h
3
 *
4
 * Created: 12.02.2015 14:15:04
5
 *  Author: u308488
6
 */ 
7
8
9
#ifndef SPI_MASTER_SLAVE_H_
10
#define SPI_MASTER_SLAVE_H_
11
12
#include <avr/io.h>
13
#include <stdlib.h>
14
#include <util/delay.h>
15
#include <avr/interrupt.h>
16
17
#include "../global.h"
18
extern volatile char spi_data_buf[50];
19
extern volatile int spi_data_pos;
20
extern volatile int status;
21
22
void spi_init_slave (void);
23
void spi_init_master (void);
24
char spi_send (char data, unsigned char cs);
25
26
#endif /* SPI_MASTER_SLAVE_H_ */
27
[c/]
28
29
ds1307.c
30
[c]
31
/*
32
ds1307 lib 0x01
33
34
copyright (c) Davide Gironi, 2013
35
36
Released under GPLv3.
37
Please refer to LICENSE file for licensing information.
38
*/
39
40
41
#include "avr/io.h"
42
#include "avr/pgmspace.h"
43
#include "util/delay.h"
44
45
#include "ds1307.h"
46
47
//path to i2c fleury lib
48
#include DS1307_I2CFLEURYPATH
49
50
/*
51
 * days per month
52
 */
53
const uint8_t ds1307_daysinmonth [] PROGMEM = { 31,28,31,30,31,30,31,31,30,31,30,31 };
54
55
/*
56
 * initialize the accellerometer
57
 */
58
void ds1307_init() {
59
  #if DS1307_I2CINIT == 1
60
  //init i2c
61
  i2c_init();
62
  _delay_us(10);
63
  #endif
64
}
65
66
/*
67
 * transform decimal value to bcd
68
 */
69
uint8_t ds1307_dec2bcd(uint8_t val) {
70
  return val + 6 * (val / 10);
71
}
72
73
/*
74
 * transform bcd value to deciaml
75
 */
76
static uint8_t ds1307_bcd2dec(uint8_t val) {
77
  return val - 6 * (val >> 4);
78
}
79
80
/*
81
 * get number of days since 2000/01/01 (valid for 2001..2099)
82
 */
83
static uint16_t ds1307_date2days(uint8_t y, uint8_t m, uint8_t d) {
84
  uint16_t days = d;
85
  for (uint8_t i = 1; i < m; ++i)
86
    days += pgm_read_byte(ds1307_daysinmonth + i - 1);
87
  if (m > 2 && y % 4 == 0)
88
    ++days;
89
  return days + 365 * y + (y + 3) / 4 - 1;
90
}
91
92
/*
93
 * get day of a week
94
 */
95
uint8_t ds1307_getdayofweek(uint8_t y, uint8_t m, uint8_t d) {
96
  uint16_t day = ds1307_date2days(y, m, d);
97
  return (day + 6) % 7;
98
}
99
100
/*
101
 * set date
102
 */
103
uint8_t ds1307_setdate(uint8_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t minute, uint8_t second) {
104
  //sanitize data
105
  if (second < 0 || second > 59 ||
106
    minute < 0 || minute > 59 ||
107
    hour < 0 || hour > 23 ||
108
    day < 1 || day > 31 ||
109
    month < 1 || month > 12 ||
110
    year < 0 || year > 99)
111
    return 8;
112
113
  //sanitize day based on month
114
  if(day > pgm_read_byte(ds1307_daysinmonth + month - 1))
115
    return 0;
116
117
  //get day of week
118
  uint8_t dayofweek = ds1307_getdayofweek(year, month, day);
119
120
  //write date
121
  i2c_start_wait(DS1307_ADDR | I2C_WRITE);
122
  i2c_write(0x00);//stop oscillator
123
  i2c_write(ds1307_dec2bcd(second));
124
  i2c_write(ds1307_dec2bcd(minute));
125
  i2c_write(ds1307_dec2bcd(hour));
126
  i2c_write(ds1307_dec2bcd(dayofweek));
127
  i2c_write(ds1307_dec2bcd(day));
128
  i2c_write(ds1307_dec2bcd(month));
129
  i2c_write(ds1307_dec2bcd(year));
130
  i2c_write(0x00); //start oscillator
131
  i2c_stop();
132
133
  return 1;
134
}
135
136
/*
137
 * get date
138
 */
139
void ds1307_getdate(uint8_t *year, uint8_t *month, uint8_t *day, uint8_t *hour, uint8_t *minute, uint8_t *second) {
140
  i2c_start_wait(DS1307_ADDR | I2C_WRITE);
141
  i2c_write(0x00);//stop oscillator
142
  i2c_stop();
143
144
  i2c_rep_start(DS1307_ADDR | I2C_READ);
145
  *second = ds1307_bcd2dec(i2c_readAck() & 0x7F);
146
  *minute = ds1307_bcd2dec(i2c_readAck());
147
  *hour = ds1307_bcd2dec(i2c_readAck());
148
  i2c_readAck();
149
  *day = ds1307_bcd2dec(i2c_readAck());
150
  *month = ds1307_bcd2dec(i2c_readAck());
151
  *year = ds1307_bcd2dec(i2c_readNak());
152
  i2c_stop();
153
}
ds1307.h
1
/*
2
ds1307 lib 0x01
3
4
copyright (c) Davide Gironi, 2013
5
6
Released under GPLv3.
7
Please refer to LICENSE file for licensing information.
8
9
References: parts of the code taken from https://github.com/adafruit/RTClib
10
*/
11
12
13
#ifndef DS1307_H
14
#define DS1307_H
15
16
//definitions
17
#define DS1307_ADDR (0x68<<1) //device address
18
19
//path to i2c fleury lib
20
#define DS1307_I2CFLEURYPATH "../i2chw/i2cmaster.h" //define the path to i2c fleury lib
21
#define DS1307_I2CINIT 1 //init i2c
22
23
//functions
24
extern void ds1307_init();
25
extern uint8_t ds1307_setdate(uint8_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t minute, uint8_t second);
26
extern void ds1307_getdate(uint8_t *year, uint8_t *month, uint8_t *day, uint8_t *hour, uint8_t *minute, uint8_t *second);
27
28
#endif

global.h
1
#ifndef GLOBAL_H_
2
#define GLOBAL_H_
3
4
#define F_CPU 16000000UL
5
6
#define ZCD_MAX 1000  // max. ZeroCross_Detect Variable d. H. max. 1000 Halbwellen dann wird die Variable wieder auf 0 gesetzt
7
            // 10000-> 10000*20ms = 200s = 3,3min; 1000 -> 1000*20ms = 20s, 4500 -> 4500*20ms = 90s = 1min30s
8
            // 3000 -> 3000*20ms = 60s = 1min
9
10
// SPI DatenVariable
11
char volatile spi_data_buf[50];
12
volatile int spi_data_pos;
13
volatile int status;
14
15
//DIMMER1 
16
#define PORT_DIMMER_1234  PORTA
17
#define DDR_DIMMER_1234    DDRA
18
#define PIN_DIMMER1      7    //PA7
19
#define PIN_DIMMER2      6    //PA6
20
#define PIN_DIMMER3      5    //PA5
21
#define PIN_DIMMER4      4    //PA4
22
23
#define PORT_DIMMER_56    PORTC
24
#define DDR_DIMMER_56    DDRC
25
#define PIN_DIMMER5      1    //PC1
26
#define PIN_DIMMER6      0    //PC0
27
28
#define SET_DIMMER1      PORT_DIMMER_1234 &= ~(1<<PIN_DIMMER1)  // set DIMMER1
29
#define CLEAR_DIMMER1    PORT_DIMMER_1234 |= (1<<PIN_DIMMER1)  // clear DIMMER1
30
#define SET_DIMMER2      PORT_DIMMER_1234 &= ~(1<<PIN_DIMMER2)  // set DIMMER2
31
#define CLEAR_DIMMER2    PORT_DIMMER_1234 |= (1<<PIN_DIMMER2)  // clear DIMMER2
32
#define SET_DIMMER3      PORT_DIMMER_1234 &= ~(1<<PIN_DIMMER3)  // set DIMMER3
33
#define CLEAR_DIMMER3    PORT_DIMMER_1234 |= (1<<PIN_DIMMER3)  // clear DIMMER3
34
#define SET_DIMMER4      PORT_DIMMER_1234 &= ~(1<<PIN_DIMMER4)  // set DIMMER4
35
#define CLEAR_DIMMER4    PORT_DIMMER_1234 |= (1<<PIN_DIMMER4)  // clear DIMMER4
36
#define SET_DIMMER5      PORT_DIMMER_56 &= ~(1<<PIN_DIMMER5)    // set DIMMER5
37
#define CLEAR_DIMMER5    PORT_DIMMER_56 |= (1<<PIN_DIMMER5)    // clear DIMMER5
38
#define SET_DIMMER6      PORT_DIMMER_56 &= ~(1<<PIN_DIMMER6)    // set DIMMER5
39
#define CLEAR_DIMMER6    PORT_DIMMER_56 |= (1<<PIN_DIMMER6)    // clear DIMMER5
40
41
// 3,3V und 5V auf SPI Stecker
42
#define PORT_SPI_CON_POWER      PORTE
43
#define DDR_SPI_CON_POWER      DDRE
44
#define PIN_SPI_CON_POWER_V5_3V3  2    // PE2
45
#define SET_SPI_CON_POWER_V5_3V3  PORT_SPI_CON_POWER &= ~(1<<PIN_SPI_CON_POWER_V5_3V3)
46
#define CLEAR_SPI_CON_POWER_V5_3V3  PORT_SPI_CON_POWER |= (1<<PIN_SPI_CON_POWER_V5_3V3)
47
48
// SPI
49
#define PORT_SPI    PORTB
50
#define DDR_SPI      DDRB
51
#define PIN_SPI_MISO  3    // PB3
52
#define PIN_SPI_MOSI  2    // PB2
53
#define PIN_SPI_SCK    1    // PB1
54
#define PIN_SPI_SS    0    // PB0
55
#define PORT_SPI_CS    PORTC
56
#define DDR_SPI_CS    DDRC
57
#define PIN_SPI_CS1    7    // PC7
58
#define PIN_SPI_CS2    6    // PC6
59
#define PIN_SPI_CS3    5    // PC5
60
61
62
//LED 1-3
63
#define PORT_LED      PORTC
64
#define DDR_LED        DDRC
65
#define PIN_LED1      2    //PC2
66
#define PIN_LED2      3    //PC3
67
#define PIN_LED3      4    //PC4
68
#define SET_LED1      PORT_LED &= ~(1<<PIN_LED1)  // set LED1
69
#define CLEAR_LED1      PORT_LED |= (1<<PIN_LED1)  // clear LED1
70
#define SET_LED2      PORT_LED &= ~(1<<PIN_LED2)  // set LED2
71
#define CLEAR_LED2      PORT_LED |= (1<<PIN_LED2)  // clear LED2
72
#define SET_LED3      PORT_LED &= ~(1<<PIN_LED3)  // set LED3
73
#define CLEAR_LED3      PORT_LED |= (1<<PIN_LED3)  // clear LED3
74
#define SWITCH_LED1      PORT_LED ^= (1 << PIN_LED1);    // LED1 umschalten
75
#define SWITCH_LED2      PORT_LED ^= (1 << PIN_LED2);    // LED2 umschalten
76
#define SWITCH_LED3      PORT_LED ^= (1 << PIN_LED3);    // LED3 umschalten
77
78
// ZeroCross_Detect
79
#define PORT_ZEROCROSSDETECT  PORTD
80
#define DDR_ZEROCROSSDETECT    DDRD
81
#define PIN_ZEROCROSSDETECT    2    //PD2
82
83
// Relais
84
#define PORT_RELAIS    PORTA
85
#define DDR_RELAIS    DDRA
86
#define PIN_RELAIS1    3    // PA3
87
#define PIN_RELAIS2    2    // PA2
88
#define PIN_RELAIS3    1    // PA1
89
#define PIN_RELAIS4    0    // PA0
90
#define SET_RELAIS1    PORT_RELAIS |= (1<<PIN_RELAIS1)    // set Relais1 
91
#define CLEAR_RELAIS1  PORT_RELAIS &= ~(1<<PIN_RELAIS1)  // clear Relais1 
92
#define SET_RELAIS2    PORT_RELAIS |= (1<<PIN_RELAIS2)    // set Relais2 
93
#define CLEAR_RELAIS2  PORT_RELAIS &= ~(1<<PIN_RELAIS2)  // clear Relais2 
94
#define SET_RELAIS3    PORT_RELAIS |= (1<<PIN_RELAIS3)    // set Relais3
95
#define CLEAR_RELAIS3  PORT_RELAIS &= ~(1<<PIN_RELAIS3)  // clear Relais3 
96
#define SET_RELAIS4    PORT_RELAIS |= (1<<PIN_RELAIS4)    // set Relais4
97
#define CLEAR_RELAIS4  PORT_RELAIS &= ~(1<<PIN_RELAIS4)  // clear Relais4 
98
99
// Analogeingang
100
#define  PORT_ADC    PORTF
101
#define DDR_ADC      DDRF
102
#define PIN_ADC8    0  // PF0
103
#define PIN_ADC7    1  // PF1
104
#define PIN_ADC6    2  // PF2
105
#define PIN_ADC5    3  // PF3
106
#define PIN_ADC4    4  // PF4
107
#define PIN_ADC3    5  // PF5
108
#define PIN_ADC2    6  // PF6
109
#define PIN_ADC1    7  // PF7
110
111
// Sonnenaufgang- Untergangs, Vorschaltgerät 
112
#define PORT_EVG    PORTB
113
#define DDR_EVG      DDRB
114
#define PIN_EVG1    5    // PB5 (OC1A)
115
#define PIN_EVG2    6    // PB6 (OC1B)
116
117
118
/********************************************************************************
119
Strukturen
120
********************************************************************************/
121
struct TIME {
122
  uint8_t hour;
123
  uint8_t minute;
124
  uint8_t second;
125
};
126
127
struct DATUM {
128
  uint8_t year;
129
  uint8_t month;
130
  uint8_t day;
131
};
132
133
// Struktur in der die SystemZeit eingetragen wird
134
struct DATUM_TIME {
135
  struct DATUM datum;
136
  struct TIME time; 
137
  int zeitzone;        // 0=Weltzeit; 1=Winterzeit; 2=Sommerzeit
138
  double geographischeLaenge;  // Nord
139
  double geographischeBreite;  // Ost
140
};
141
142
struct TERRARIUMEINSTELLUNG {
143
  int AUTO;              // Wenn dieser Wert 1 dann wird TAGESLICHT_PROZENT, DIMMEROUT_TAGESLICHT_ON, SONNENAUFGANGSDAUER, 
144
                    // SONNENUNTERGANGSDAUER, ONTIME und OFFTIME automatisch gesezt
145
  int WINTERSCHLAFZEIT;        // Wenn dieser Wert 1 dann werden die TEMP auf 20Grad geregelt
146
  int WINTERSCHLAF_TEMP;        // Winterschlaftemperatureinstellung  
147
  int TAGESLICHT_PROZENT;        // Tageslichtprozent (abhängig von der Uhrzeit)
148
  int DIMMEROUT_TAGESLICHT_ON;    // Einschalten des Tageslicht (abhängig von der Uhrzeit)
149
                    // 0 = Aus; 1> = An; 2 = Dämmerung startet (Sonnenuntergang)
150
  int DIMMEROUT_TEMPERATUR_A_PROZENT;  // Heizlampe A (abhängig von der Uhrzeit)
151
  int DIMMEROUT_TEMPERATUR_B_PROZENT;  // Heizlampe B (abhängig von der Uhrzeit) 
152
  int MAX_TEMP_A;            // Max. Temperatur bei max. Sonnenstand (100%) an Heizlampe A
153
  int MAX_TEMP_B;            // Max. Temperatur bei max. Sonnenstand (100%) an Heizlampe B
154
  int MIN_TEMP_A;            // Min. Temperatur bei Sonnenstand (0%) an Heizlampe A
155
  int MIN_TEMP_B;            // Min. Temperatur bei Sonnenstand (0%) an Heizlampe B
156
  int TEMP_A;              // aktuelle Temperatur A
157
  int TEMP_B;              // aktuelle Temperatur B
158
  int TEMP_C;              // aktuelle Temperatur C
159
  int TEMP_D;              // aktuelle Temperatur D            
160
  struct TIME ontime;          // Einschaltzeitpunkt z.B. 5Uhr
161
  struct TIME offtime;        // Ausschaltzeitpunkt z.B. 22Uhr  
162
  int sonnenaufgang[10];        // [0]=Aufgang_Stunde, [1]=Aufgang_Minuten; [2-3]=bürgerliche_daemmerung; [4-5]=nautische_daemmerung; 
163
                    // [6-7]=astronomische_daemmerung; [8]=Aufgangsdauer in min  
164
  int sonnenuntergang[10];      // [0]=Untergang_Stunde, [1]=Untergang_Minuten; [2-3]=bürgerliche_daemmerung; [4-5]=nautische_daemmerung; 
165
                    // [6-7]=astronomische_daemmerung; [8]=Untergangsdauer in min    
166
  int FEUCHTIGKEIT;          // Feuchtigkeitswert
167
};
168
169
typedef struct DATUM_TIME SYSTEMTIME;  // Typendefination
170
typedef struct TERRARIUMEINSTELLUNG TERRARIUMEINSTELLUNG_A;
171
typedef struct TERRARIUMEINSTELLUNG TERRARIUMEINSTELLUNG_B;
172
173
/********************************************************************************
174
Structuren definieren
175
********************************************************************************/
176
SYSTEMTIME systemtime;      
177
TERRARIUMEINSTELLUNG_A terrariumsteuerung_a;
178
TERRARIUMEINSTELLUNG_B terrariumsteuerung_b;

Hauptprogramm
1
/********************************************************************************
2
Hauptprogram
3
********************************************************************************/
4
int main(void)
5
{  
6
  cli();            // Disable all interrupts
7
  initPort();          // Inizalisierung der Ports
8
  spi_init_slave ();      // SPI Inizalisierung als Slave    
9
  initZerocrossdetection();  // Nulldurchgangserkennung fuer Dimmereinschaltung
10
  InitADC();          // fuer Temperaturmessung
11
  ds1307_init();        // init RTC ds1307     
12
  InitEVG();          // Inizialisirung von des EVG          
13
  sei();            // enable all interrupts
14
  
15
  // RTC Werkseinstellungen schreiben
16
  systemtime.datum.year = 15;
17
  systemtime.datum.month = 04;
18
  systemtime.datum.day = 02;  
19
  systemtime.time.hour = 13;
20
  systemtime.time.minute = 06;
21
  systemtime.time.second = 00;
22
  systemtime.zeitzone = 2;
23
  systemtime.geographischeLaenge = 13.5;  // Berlin
24
  systemtime.geographischeBreite = 52.5;  // Berlin
25
while(1)
26
    {  
27
    SWITCH_LED1;
28
    _delay_ms(500);
29
// Einlesen der Systemzeit und in Struktur schreiben
30
    ds1307_getdate(&systemtime.datum.year, &systemtime.datum.month, &systemtime.datum.day, &systemtime.time.hour, &systemtime.time.minute, &systemtime.time.second);
31
  }
32
}

Auf der Gegenseite
spi_master_slave.c
1
// Senden von Daten über SPI
2
// Beispiel: spi_data = spi_send ("TEST\n", PIN_SPI_CS3); // senden von einem String auf der SPI-Schnittstelle
3
void spi_send_string(char string[], char *antwort, int cs)
4
{
5
  uint8_t length;
6
  uint8_t i;
7
  int MAXANTEEORTLAENGE = 50;
8
  
9
  length = strlen(string);
10
  
11
  // Anfrage der Daten senden
12
  for(i=0; i < length; i++)
13
  {
14
    spi_send(string[i], cs);    // Senden der Zeichenketten und Antwort in antwort speichern
15
    _delay_ms(1);          // Zeitverzögerung wird benötigt da sonst das Antworten vom Slave nicht funktioniert     
16
  }
17
  _delay_ms(2);
18
    
19
  // Antwort empfangen
20
  for(i=0; i <= MAXANTEEORTLAENGE; i++)
21
  {   
22
    if (i==0)  // Erste Byte vernachlässigen
23
    {
24
      spi_send('0', cs);  // Dummybyte senden
25
      _delay_ms(1);    // Zeitverzögerung wird benötigt da sonst das Antworten vom Slave nicht funktioniert
26
    }
27
    else 
28
    {  
29
      if (i==MAXANTEEORTLAENGE)
30
      {
31
        *antwort = spi_send('\n', cs);  // Senden der Zeichenketten und Antwort in antwort speichern
32
        _delay_ms(1);          // Zeitverzögerung wird benötigt da sonst das Antworten vom Slave nicht funktioniert
33
      }
34
      else
35
      {
36
        *antwort = spi_send('0', cs);  // Senden der Zeichenketten und Antwort in antwort speichern
37
        antwort++;
38
        _delay_ms(1);          // Zeitverzögerung wird benötigt da sonst das Antworten vom Slave nicht funktioniert
39
      }
40
    }
41
  }
42
}
43
44
// Senden von Daten über SPI
45
// Beispiel: spi_data = spi_send ('T', PIN_SPI_CS3); // senden von einem Byte auf der SPI-Schnittstelle
46
char spi_send (unsigned char data, int cs)
47
{
48
  PORT_SPI &= ~(1<<cs);        // SS am Slave Low --> Beginn der Übertragung
49
    SPDR = data;            // Daten ins Buffer-Register laden und senden mittels Interrupt
50
  _delay_us(10);
51
  while (!(SPSR & (1<<SPIF)));
52
  _delay_us(10);
53
  //_delay_ms(1);              // Zeitverzögerung wird benötigt da sonst das Antworten vom Slave nicht funktioniert
54
  PORT_SPI |= (1<<cs);        // SS High --> Ende der Übertragung
55
  return (SPDR);            // Return received data  
56
}
57
58
// Initialize SPI Master Device
59
void spi_init_master (void)            
60
{
61
  DDR_SPI |= (1<<PIN_SPI_MOSI) | (1<<PIN_SPI_SCK) | (1<<PIN_SPI_CS1B) | (1<<PIN_SPI_CS3);  // MOSI und SCK als Ausgang definieren
62
  DDR_SPI &= ~(1<<PIN_SPI_MISO);                              // MISO als Eingang definieren
63
  PORT_SPI |= (1<<PIN_SPI_CS3);        // SS High
64
  
65
  //SPCR = (1<<MSTR) | (1<<SPIE) | (1<<SPE);    // Set as Master, Enable Interrupts, Enable SPI
66
  SPCR =  (1<<SPE) | (1<<MSTR) | (1<<SPR1);    // Set as Master, Enable Interrupts, Enable SPI
67
  SPSR |= (0 << SPIF);              // SPIF zum Start löschen 
68
}
aufruf der daten
1
char tmp[22];
2
spi_send_string("TIME\n", &tmp, PIN_SPI_CS3);  // Senden von Time\n an Haupteinheit
3
display_write(tmp,0,5);

Denke mal das das alles Relevante ist was benötigt wird um den Fehler zu 
finden :-) Falls noch was fehlen sollte sofort bescheid sagen dann 
reiche ich es einfach nach.

Dankeschön für eure Hilfe :-)

von GC (Gast)


Lesenswert?

Ach ja nochwas
als ich versucht habe den String empfangenen String zu zerteilen 
(Trennung der einzelnen Zahlen habe ich mit einem ":" gemacht, denke ich 
mal das ich auch ein Fehler mache vielleicht könnte ja jemand auch da 
drüber schaue4n
1
void readsystemtime()
2
{
3
  char systemtimebuffer[50];
4
  char *psystemtimetoken[50];  
5
  // Systemzeit von Basisplatine abfragen und in Struktur schreiben
6
  spi_send_string("TIME\n", &systemtimebuffer, PIN_SPI_CS3);  // Senden von Time\n an Haupteinheit
7
  display_write(systemtimebuffer,0,5);  
8
     
9
  psystemtimetoken[0] = strtok(systemtimebuffer, ":");
10
  while ( i <= 50 ) 
11
  {
12
    i++;
13
    psystemtimetoken[i] = strtok(NULL, ":");
14
  }
15
  i=0;
16
systemtime.datum.day = atoi(psystemtimetoken[1]);        // Tag
17
  systemtime.datum.month = atoi(psystemtimetoken[2]);        // Monat
18
  systemtime.datum.year = atoi(psystemtimetoken[3]);        // Jahr
19
  systemtime.time.hour = atoi(psystemtimetoken[4]);        // Stunde
20
  systemtime.time.minute = atoi(psystemtimetoken[5]);        // Minute
21
  systemtime.time.second = atoi(psystemtimetoken[6]);        // Sekunde
22
  systemtime.zeitzone = atoi(psystemtimetoken[7]);        // Zeitzone
23
}

ich habe vorhin in der spi_master_slave.c die for-Schleifenbedingung 
geändert und nun verschwinden keine Zahlen mehr doch meine Anzeige zeigt 
nun 08:4:15:8:0:36 woher kommt denn die erste null? kann es mir leidr 
nicht erklären :-(
1
void spi_send_string(char string[], char *antwort, int cs)
2
{
3
  uint8_t length;
4
  uint8_t i;
5
  int MAXANTEEORTLAENGE = 50;
6
  
7
  length = strlen(string);
8
  
9
  // Anfrage der Daten senden
10
  for(i=0; i < length; i++)
11
  {
12
    spi_send(string[i], cs);    // Senden der Zeichenketten und Antwort in antwort speichern
13
    _delay_ms(1);          // Zeitverzögerung wird benötigt da sonst das Antworten vom Slave nicht funktioniert     
14
  }
15
  _delay_ms(2);
16
    
17
  // Antwort empfangen
18
  for(i=0; i < MAXANTEEORTLAENGE; i++)
19
  {   
20
    if (i==0)  // Erste Byte vernachlässigen
21
    {
22
      spi_send('0', cs);  // Dummybyte senden
23
      _delay_ms(1);    // Zeitverzögerung wird benötigt da sonst das Antworten vom Slave nicht funktioniert
24
    }
25
    else 
26
    {  
27
      if (i==MAXANTEEORTLAENGE-1)
28
      {
29
        *antwort = spi_send('\n', cs);  // Senden der Zeichenketten und Antwort in antwort speichern
30
        _delay_ms(1);          // Zeitverzögerung wird benötigt da sonst das Antworten vom Slave nicht funktioniert
31
      }
32
      else
33
      {
34
        *antwort = spi_send('0', cs);  // Senden der Zeichenketten und Antwort in antwort speichern
35
        antwort++;
36
        _delay_ms(1);          // Zeitverzögerung wird benötigt da sonst das Antworten vom Slave nicht funktioniert
37
      }
38
    }
39
  }
40
}

von Pete K. (pete77)


Lesenswert?

GC schrieb:
> while ( i <= 50 )
>   {
>     i++;
>     psystemtimetoken[i] = strtok(NULL, ":");
>   }
>   i=0;

Und da schmeisst der Compiler keine Warnung?

von GC (Gast)


Lesenswert?

nein keine Errors und keine keine Warnings
Warum?

von Marcel P (Gast)


Lesenswert?

Mir ist eine andere Sache aufgefallen, da ich auch gerade mit DS1307 
arbeite.

Nach dem Senden der Adresse zum Lesen der Uhr sendest du eine 
Stop-Condition, und danach eine Repeated Start Condition.
Das Datenblatt von Maxim sagt auf Seite 13 nichts von der 
Stop-Condition. Vielleicht kommt die Uhr durcheinander? Kann es jetzt 
nicht testen, da nicht am Ort des Geschehens.

von GC (Gast)


Lesenswert?

hmm wenn ich ehrlich bin glaube ich so langsam das bei der Übertragung 
mit SPI etwas schief läuft!
Wenn ich von meiner Displayplatine ein TEMPA\n sende erwate ich ja 
eigentlich als Antwort A:WertB:WertC:WertD:Wert (Wert = errechnete 
Temperatur)
Aber als Antwort erscheint auf dem Display 0:WertB:WertC:WertD:Wert

Auf der Basisplatine Baue ich ja die Antwort zusammen. Da dürfte 
einglich kein Fehler sein oder habe ich da was übersehen?

von GC (Gast)


Lesenswert?

Marcel P schrieb:
> Mir ist eine andere Sache aufgefallen, da ich auch gerade mit
> DS1307
> arbeite.
>
> Nach dem Senden der Adresse zum Lesen der Uhr sendest du eine
> Stop-Condition, und danach eine Repeated Start Condition.
> Das Datenblatt von Maxim sagt auf Seite 13 nichts von der
> Stop-Condition. Vielleicht kommt die Uhr durcheinander? Kann es jetzt
> nicht testen, da nicht am Ort des Geschehens.

Habe ich ausprobiert und das Ergebnis ist das Gleiche bzw. macht keinen 
Unterschied... Schade wäre ja zu schön gewesen :-)

von Marcel P (Gast)


Lesenswert?

GC schrieb:
1
 char spi_send (unsigned char data, int cs)
2
 {
3
   PORT_SPI &= ~(1<<cs);
4
     SPDR = data;
5
   _delay_us(10);
6
   while (!(SPSR & (1<<SPIF)));
7
   _delay_us(10);
8
   //_delay_ms(1);
9
   PORT_SPI |= (1<<cs);
10
   return (SPDR);
11
 }

Antwortet der Slave vielleicht nicht rechtzeitig? (Gesperrte Interrupts, 
weil du etwas anderes bevorzugt behandeln willst?)
Ist vorher das Interrupt-Flag korrekt gelöscht, sodass nicht gleich die 
Warteschleife übersprungen wird?

In dem Fall beinhaltet SPDR nämlich noch '0', wie in spi_send_string() 
als Dummy gesendet wird:

GC schrieb:
1
void spi_send_string(...
2
[...]
3
       if (i==MAXANTEEORTLAENGE-1)
4
       {
5
         *antwort = spi_send('\n', cs);
6
         _delay_ms(1);
7
       }
8
       else
9
       {
10
         *antwort = spi_send('0', cs);
11
         antwort++;
12
         _delay_ms(1);
13
       }
14
     }
15
   }
16
 }

Sofern ich jetzt nicht verkehrt denke und den Ablauf richtig verfolgt 
habe. :D

von Karl H. (kbuchegg)


Lesenswert?

Da
1
      spi_data_buf[spi_data_pos++] = data;  
2
      if (data == '\n')
3
      {
4
        spi_data_pos = 0;
5
        if (strcmp(spi_data_buf,"TIME\n") == 0)

fehlt auf jeden Fall noch die 0-Terminierung des Strings, ansonsten ist 
es Essig mit der Verwendung der str... Funktionen.
1
      spi_data_buf[spi_data_pos++] = data;  
2
      if (data == '\n')
3
      {
4
        spi_data_buf[spi_data_pos] = '\0';   // <-----
5
        spi_data_pos = 0;
6
        if (strcmp(spi_data_buf,"TIME\n") == 0)

den \n würde ich auch nicht mit abspeichern. Wozu? Es gibt keinen Grund 
dazu. Dadurch werden nur die Vergleichsstrings bei den strcmp läner und 
du darfst dort auch in Zukunft den \n nicht vergessen.

von Karl H. (kbuchegg)


Lesenswert?

Die Steuerung hier
1
  else if (status == 1) // Befehl empfangen um Antwort zurück zu senden
2
  {
3
    data = SPDR;  // Ankommende Daten über SPI in Buffer Schreiben
4
    
5
    SPDR = spi_data_buf[spi_data_pos];  // Antwort senden
6
    spi_data_pos++;
7
    if (data == '\n')
8
    {
ist auch seltsam.
Die Gegenstelle teilt mit, wann die Übertragung abgeschlossen ist. Hmm.

von Karl H. (kbuchegg)


Lesenswert?

Hmm
1
extern volatile char spi_data_buf[50];

50 Zeichen.

Ich habs nicht nachgezählt, aber 50 Zeichen scheinen mir für diesen Wust
1
          sprintf(spi_data_buf, "%d%s%d%s%d%s%d%s%d%s%d%s%d%s%d%s%d%s%d%s%d%s%d%s%d%s%d%s%d%s%d%s%d%s%d%s%d%s%d%s%d%s%d%s%d%s%d%s%d%s%d%s%d%s%d%s%d%s%d%s%d%s%d%s%d%s%d%s%d%s%d%s%d%s%d%s"

nicht sehr viel zu sein.

PS: die ganzen %s, in denen du jweils einen ":" reinschiebst, kannst du 
dir auch sparen. Fixe Zeichen schreibt man gleich in den Format String. 
Dann kann man sie nicht vergessen und die Argumentlisten werden kürzer.
1
          sprintf(spi_data_buf, "%d:%d:%d:%d ........

so dermassen lange Strings sind zwar nicht verboten. Ich würde sie 
trotzdem nicht benutzen. Da soll die Gegenstelle ruhig ein paar mal nach 
spezifischen Daten fragen. Die kann man ja in thematische Gruppen 
zusammenfassen.
So dermassen lange printf sind in der Wartung sehr fehleranfällig.

Und noch ein PS:
Man kann in C einen sehr langen String in mehrere Teile aufteilen!
Anstatt
1
     "Hallo World"
darf man auch den String in mehreren Teilen schreiben
1
     "Hallo "
2
     "World"
Beachte: Da ist kein , zwischen den Teilstrings. In so einem Fall ist 
der Compiler verpflichtet die einzelnen Teilstrings zu einem kompletten 
String zusammenzusetzen. Die beiden Beispiele sind also völlig 
identisch.
Der Vorteil für dich: Du kannst deinen elends langen Formatstring in 
Teile aufbrechen, damit du nicht den Überblick verlierst, welches %d zu 
welchem Argument gehört.

: Bearbeitet durch User
von Karl H. (kbuchegg)


Lesenswert?

Solche Dinge
1
void spi_send_string(char string[], char *antwort, int cs)
2
{
3
  uint8_t length;
4
  uint8_t i;
5
  int MAXANTEEORTLAENGE = 50;
6
  
7
  length = strlen(string);
8
  
9
  // Anfrage der Daten senden
10
  for(i=0; i < length; i++)
11
  {
12
    spi_send(string[i], cs);    // Senden der Zeichenketten und Antwort in antwort speichern
13
    _delay_ms(1);          // Zeitverzögerung wird benötigt da sonst das Antworten vom Slave nicht funktioniert     
14
  }
15
  _delay_ms(2);
16
    
17
  // Antwort empfangen
18
  for(i=0; i <= MAXANTEEORTLAENGE; i++)
19
  {

sind keine gute Idee. Bei Übertragungen, speziell bei Textübertragungen 
willst du nicht eine fixe Anzahl an Bytes hin und her schicken.

Sondern: Der eine fordert Daten an und beendet seine Anforderung zb mit 
einem \n. Der andere antwortet mit den Daten die er hat und beendet die 
Antwort ebenfalls zb mit einem \n. Der jeweilige Empfangspart muss nur 
darauf achten, dass er seinen Buffer nicht überläuft. Aber mit so 
Annahmen, dass man so und so viele Zeichen zurück bekommen würde, tut 
man sich keinen Gefallen. Das ist alles ziemlich schlecht erweiterbar. 
Wenn die Datenmenge zunimmt, musst du dann an allen Ecken und Enden 
nachbessern. Viel einfacher ist es, wenn die Kommunikation so 
funktioniert, dass jeder einfach das sendet was er hat und hinten nach 
kommt noch ein Abschlusszeichen, das der Gegenstelle signalisiert "Das 
wars".

Und ja. Dein Monster-sprintf könnte schon etwas dauern. D.h. du solltest 
der Gegenstelle auch nach der Anforderung die Zeit geben, den 
durchzuführen. Oder eben nicht eine einzige Anfrage nach 'Allem' 
stellen, sondern einfach mehrere Anfragen für Themenkreise. Dann werden 
auch die Antworten kürzer und du läufst bei Erweiterungen wenigr Gefahr, 
irgendwo einen Buffer zu überlaufen.

von Karl H. (kbuchegg)


Lesenswert?

1
void readsystemtime()
2
{
3
  char systemtimebuffer[50];
4
  char *psystemtimetoken[50];  
5
  // Systemzeit von Basisplatine abfragen und in Struktur schreiben
6
  spi_send_string("TIME\n", &systemtimebuffer, PIN_SPI_CS3);  // Senden von Time\n an Hauptei

Die Funktion spi_send_string sollte die Buffergröße beim Aufruf 
mitbekommen und nicht davon ausgehen, dass die immer 50 ist. Spart 
Speicher. Denn eine Uhrzeit wird wohl kaum 50 Zeichen lang sein.

Das hier
1
  psystemtimetoken[0] = strtok(systemtimebuffer, ":");
2
  while ( i <= 50 ) 
3
  {
4
    i++;
5
    psystemtimetoken[i] = strtok(NULL, ":");
6
  }
7
  i=0;
8
systemtime.datum.day = atoi(psystemtimetoken[1]);        // Tag
9
  systemtime.datum.month = atoi(psystemtimetoken[2]);        // Monat
10
  systemtime.datum.year = atoi(psystemtimetoken[3]);        // Jahr
11
  systemtime.time.hour = atoi(psystemtimetoken[4]);        // Stunde
12
  systemtime.time.minute = atoi(psystemtimetoken[5]);        // Minute
13
  systemtime.time.second = atoi(psystemtimetoken[6]);        // Sekunde
14
  systemtime.zeitzone = atoi(psystemtimetoken[7]);        // Zeitzone
15
}

ist wieder extrem gefährlich. Du gehst davon aus, dass in der Antwort 
schon so viele ':' enthalten sein werden, dass du alle Teile extrahieren 
kannst.

machs doch nicht so kompliziert mit dem Pointer-Array. Wenn du das ein 
wenig lesbarer machen willst, dann führ dir zb eine Funktion ein
1
uint8_t parseInt()
2
{
3
  char* ptr;
4
  uint8_t result = 0xFF;
5
6
  ptr = strtok( NULL, ":" );
7
  if( ptr )
8
    result = atoi( ptr );
9
10
  return result;
11
}

damit schreibt sich dann deine Zeitroutine so
1
void readsystemtime()
2
{
3
  char systemtimebuffer[50];
4
5
  // Systemzeit von Basisplatine abfragen und in Struktur schreiben
6
  spi_send_string("TIME\n", systemtimebuffer, sizeof(systemtimebuffer), PIN_SPI_CS3); 
7
     
8
  strtok( systemtimebuffer, ":" );             // erster Wert ist dummy
9
  systemtime.datum.day   = parseInt();
10
  systemtime.datum.month = parseInt();
11
  systemtime.datum.year  = parseInt();
12
  systemtime.time.hour   = parseInt();
13
  systemtime.time.minute = parseInt();
14
  systemtime.time.second = parseInt();
15
  systemtime.zeitzone    = parseInt();
16
}

Wenn du je in der Ausgabe ein 0xFF (bzw. 255) siehst, dann weisst du, 
dass in der Antwort was nicht gestimmt hat. Der Code ruiniert aber sonst 
nichts, weil er atoi nicht mit einem NULL Pointer aufruft.

Dafür kannst du dir die Kommentare hier
1
...
2
  systemtime.datum.month = atoi(psystemtimetoken[2]);        // Monat
3
...
sparen. Da steht 2 mal das gleiche. Die Zuweisung geht an eine Variable 
die 'month' heisst. Da brauchst du nicht dazuschreiben, dass es sich um 
das Monat handelt. Das kann jeder der Lesen kann auch so sehen.

Genauso wie auch klar ist, was eine Funktion spi_send_string machen 
wird. Das brauchst du nicht kommentieren. Aus
1
  spi_send_string("TIME\n", systemtimebuffer, sizeof(systemtimebuffer), PIN_SPI_CS3);
ist für jeden klar ersichtlich ablesbar, was hier passiert. Wenn du 
unbedingt noch ausdrücken willst, dass hier ein Kommando gesendet wird, 
dann nenn die Funktion nicht spi_send_string sondern spi_send_command. 
Generell gibt man immer der Variangte den Vorzug, bei dem man das was 
man ausdrücken möchte, direkt im Code ausdrückt und nicht in einem 
Kommentar.

: Bearbeitet durch User
von Karl H. (kbuchegg)


Lesenswert?

Die Antwort auf eine TIME\n Anfrage muss natürlich auch die : enthalten. 
Sonst kann die Gegenstelle mittels strtok da nichts auseinanderpfriemeln
1
...
2
        if (strcmp(spi_data_buf,"TIME\n") == 0)
3
        {
4
          sprintf( spi_data_buf, ":%d:%d:%d"
5
                                 ":%d:%d:%d"
6
7
                     systemtime.datum.day, systemtime.datum.month, systemtime.datum.year,
8
                     systemtime.time.hour, systemtime.time.minute, systemtime.time.second);
9
10
          status = 1;  // Nächste Übertragung wird Antwort gesendet  
11
        }

und wie gesagt: ich würde da dann auch noch einen \n nachschieben, damit 
die Gegenstelle weiss, wann die Übertragung fertig ist.
1
        if (strcmp(spi_data_buf,"TIME\n") == 0)
2
        {
3
          sprintf( spi_data_buf, ":%d:%d:%d"
4
                                 ":%d:%d:%d\n"
5
...

von GC (Gast)


Lesenswert?

Wou wieviele Antworten....
Danke erstmals im Vorraus für euer ganzen Hilfen....
Werde die Anmerkungen sofort umsetzen um meinen Code ausfühlrlicher zu 
machen... War einiges dabei was ich so garnicht gesehen hatte :-)

Das Problem mit der 0, also das ich eine Anwort bekomme wie 
0:WertB:WertC:WertD:Wert habe ich gelöst indem ich beim SPI-Master 
einfach ein wenig länger warte damit mein Slave genug Zeit hat die 
Antwort vorzubereiten.Scheint mir merkwürdig zu sein aber es 
funktioniert!

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.