Real Time Clock RV3149-C3 Fehlverhalten ?

OP #7400800
Lesenswert?

Hallo, ich habe ein seltsames Verhalten des RTC RV3149-C3 von Microchrystal. Der hier gezeigte Code funktioniert. Aber nur mit dem zusätzlichen Schreiben/Lesen eines Bytes zwischen Byte Befehl und Byte Daten Sekunden. Wenn ich die beiden Zeilen weglassen, so wie es im Datenblatt auch steht, dann sind alle Werte um 1 Byte versetzt, also z.B. die cnt_sec werden dann nur jede Minute hochgezählt.

Wer findet den Fehler im Code ?

1
#define SECONDS         0b000
2
#define RTC_READ        0b10000000
3
#define CLOCK_PAGE      0b00001000
4

5
//----------SPI schreiben-------------------------------------------------
6
void SPI2Put (unsigned char wert)
7
{
8
   SPI2BUF = wert;                  // Schreiben
9
}
10
//-----------SPI  lesen---------------------------------------------------
11
unsigned char SPI2Get (void)
12
{
13
    while(!SPI2STATbits.SPITBE);     // Warten bis Sendepuffer leer
14
    return SPI2BUF;
15
}
16

17
//---------------------RV3148 READ------------------------------------------
18
void RTCread(void)              // Datum und Uhrzeit aus RTC lesen
19
    {
20
    volatile unsigned char dummy ;
21
    SS_RTC_Set();               //   Chip select RTC active high!!
22

23
    SPI2Put (RTC_READ | CLOCK_PAGE | SECONDS );
24
    dummy = SPI2Get();        // Empfanspuffer leeren
25
    dummy = dummy;
26

27
    //------Das sind die fragwürdigen 2 Zeilen
28
    SPI2Put(0);
29
    dummy = SPI2Get();        // Empfanspuffer leeren
30
    
31
        SPI2Put(0);
32
  cnt_sec = BCD2BYTE (SPI2Get());
33
  SPI2Put(0);
34
        cnt_minute = BCD2BYTE (SPI2Get());
35
  SPI2Put(0);
36
  cnt_hour = BCD2BYTE (SPI2Get());
37
  SPI2Put(0);
38
  cnt_date =  BCD2BYTE (SPI2Get());
39
  SPI2Put(0);
40
  cnt_day =  BCD2BYTE (SPI2Get());
41
  SPI2Put(0);
42
  cnt_month =  BCD2BYTE (SPI2Get());
43
  SPI2Put(0);
44
  cnt_year = 2000 + BCD2BYTE (SPI2Get());
45
       
46
        SS_RTC_Clear();     //   Chip select RTC active high!!
47
    }
Moderator (Firma: Titel) Persönliche Seite #7400884
Lesenswert?

Dirk B. schrieb:

Wird nicht beim Schreiben (SPI2PUT) bei SPI auch gleichzeitig ein Byte gelesen?

Immer. Weil SPI ja nur gekoppelte Schieberegister sind, kommt mit jedem Bit, das vom Master gesendet wird, auch ein Bit vom Slave zurück. Und deshalb wird immer ein SPI2Put() gemacht und danach gelesen.

Dirk F. schrieb:

RV3149-C3 von Microchrystal

Die Firma hat kein h im Namen.

Wer findet den Fehler im Code ?

Wie sehen denn die fraglichen beiden SPI-Funktionen aus, in denen der Fehler passiert? Besonders SPI2Get() ist interessant. Ist da ein zusätzlicher Buffer drin? Was passiert, wenn du den Puffer mit SPI2Get() zweimal hintereinander ausliest?

Und ganz interessant ist auch: was siehst du mit dem Oszi/LA auf den Busleitungen? Verhalten die sich so wie in der Appnote angegeben?

Moderator (Firma: Titel) Persönliche Seite #7400957
Lesenswert?

Dirk F. schrieb:

Die Funktion ist im o.g Quellcode gezeigt...

Ah, jetzt ja, eine Insel!

while(!SPI2STATbits.SPITBE); // Warten bis Sendepuffer leer

Vielleicht solltest du da vor dem Lesen warten, bis der Empfangspuffer voll ist. Denn je nach SPI-Implementation kann das Einlesen eines Bits durchaus einen halben SPI-Takt-Zyklus später passieren.

Dirk F. schrieb:

Geht leider nicht. Bekomme aufgrund der geringen Größe (0,4 mm Pinabstand) keine Testpins fürs Oszi angelötet

Die Uhr hat doch 0,8 mm Pinabstand. Mit einer passenden Lupe sollte das gehen...

#7400963
Lesenswert?

unsigned char SPI2Get (void) { while(!SPI2STATbits.SPITBE); // Warten bis Sendepuffer leer return SPI2BUF; }

Hmm. Das ist aber nur die Transmitt buffer empty. Ich würde hier noch auf den SPIRXIF warten, damit man sicher ist dass da etwas gibt.

Ausserdem würde ich da nach dem Zugriff auf Registers immer inen _sync() machen. Ich habe da immer Angst dass eventuell etwas nicht herausgeschrieben wird, und irgendwo noch wartet.

Und ja eigentlich brauchst du nur: unsigned char SPI2PutGet(unsigned char data) { SPI2BUF = wert; // Schreiben _sync(); while(!SPI2STATbits.SPITBE); // Warten bis Sendepuffer leer while(!SPI2STATbits.SPIRXIF); // Warten bis Sendepuffer leer return SPI2BUF; }

Moderator (Firma: Titel) Persönliche Seite #7401129
Lesenswert?

Dirk F. schrieb:

Oder ?

Ich hätte schon lang einen Draht an den SPI-Clock und den MISO gelötet und einen an irgendeinen einen IO-Pin des µC. Und dann diesen IO-Pin in der Routine SPI2Get() gesetzt und wieder zurückgesetzt:

1
unsigned char SPI2Get (void)
2
{
3
    set_debugpin();
4
    while(!SPI2STATbits.SPITBE);     // Warten bis Sendepuffer leer
5
    clear_debugpin();
6
    return SPI2BUF;
7
}

Und dann gemessen, ob an dieser Stelle tatsächlich schon das letzte Bit angekommen ist.

Dirk F. schrieb:

Der SPI Puffer hat wohl 16 Byte. Da vermute ich

Wieso musst du das vermuten? Es gibt doch ein Datenblatt.

Oder ?

Wäre ja schon arg ungeschickt, wenn erst der ganze Puffer gefüllt würde, und dann per Flag signalisiert würde, dass er schnell geleert werden muss. aber keine Sorge: man kann verschiedene "Ansprechschwellen" einstellen.

Und die Wortlänge kann mit 8, 16 und 32 Bit eingestellt werden.

Peter D. schrieb:

SPIRBF

Das würde ich auch probieren...

Angehängte Dateien:
Moderator (Firma: Titel) Persönliche Seite #7401538
Lesenswert?

Der hat was mit der Speicherverwaltung im µC zu tun und besänftigt hier lediglich "die Angst dass eventuell etwas nicht herausgeschrieben wird, und irgendwo noch wartet."

Wenn die Register aber anständig volatile deklariert sind, dann geht das auch ohne solche Placebobefehle.

Der Witz daran: wenn der Befehl ausreichend viel Zeit braucht, dann könnte der hier evtl. sogar helfen. Und wenn das zufälligerweise so hingeworden ist, merkt sich der Programmierer: wenns klemmt einfach mal diesen Befehl einstreuen.

Angehängte Dateien:
OP #7401540
Lesenswert?

Hallo, danke für eure Hilfe. So funktioniert es:

1
//----------SPI schreiben-------------------------------------------------
2
void SPI2Put (unsigned char wert)
3
{
4
   SPI2BUF = wert;                  // Schreiben
5
}
6
//-----------SPI  lesen---------------------------------------------------
7
unsigned char SPI2Get (void)
8
    {
9
    while(SPI2STATbits.SPIRBE);     // Warten wenn Empfangspuffer leer
10
    return SPI2BUF;
11
    }
12

13
//---------------------RV3148 READ------------------------------------------------
14
void RTCread(void)              // Datum und Uhrzeit aus RTC lesen
15
    {
16
    volatile unsigned char dummy ;
17
    SS_RTC_Set();               //   Chip select RTC active high!!
18

19
//-------------------alte-------------------------------------
20
    SPI2Put (RTC_READ | CLOCK_PAGE | SECONDS );
21
    dummy = SPI2Get();        // Empfanspuffer leeren
22
    dummy = dummy;
23
    
24
    SPI2Put(0);
25
  cnt_sec = BCD2BYTE (SPI2Get());
26
  SPI2Put(0);
27
    cnt_minute = BCD2BYTE (SPI2Get());
28
  SPI2Put(0);
29
  cnt_hour = BCD2BYTE (SPI2Get());
30
  SPI2Put(0);
31
  cnt_date =  BCD2BYTE (SPI2Get());
32
  SPI2Put(0);
33
  cnt_day =  BCD2BYTE (SPI2Get());
34
  SPI2Put(0);
35
  cnt_month =  BCD2BYTE (SPI2Get());
36
  SPI2Put(0);
37
  cnt_year = 2000 + BCD2BYTE (SPI2Get());
38

39
    SS_RTC_Clear();     //   Chip select RTC active high!!
40
    }
OP #7401542
Lesenswert?

Noch besser so:

1
//----------SPI schreiben-------------------------------------------------
2
void SPI2Put (unsigned char wert)
3
{
4
   while(!SPI2STATbits.SPITBE);     // Warten wenn Sendepuffer nicht leer ist
5
   SPI2BUF = wert;                  // Schreiben
6
}
7
//-----------SPI  lesen---------------------------------------------------
8
unsigned char SPI2Get (void)
9
    {
10
    while(SPI2STATbits.SPIRBE);     // Warten wenn Empfangspuffer leer ist
11
    return SPI2BUF;                 // Lesen
12
    }
Moderator (Firma: Titel) Persönliche Seite #7401555
Lesenswert?

Peter D. schrieb:

Da beides eh nie getrennt ausgeführt werden kann und darf

Wie ich schrieb:

Weil SPI ja nur gekoppelte Schieberegister sind, kommt mit jedem Bit, das vom Master gesendet wird, auch ein Bit vom Slave zurück.

Das macht den Code dann irgendwie auch leserlicher:

1
//----------SPI schreiben-------------------------------------------------
2
unsigned char SPI2TxRx (unsigned char wert)
3
{
4
   while(!SPI2STATbits.SPITBE);    // Warten wenn Sendepuffer nicht leer 
5
   SPI2BUF = wert;                 // Schreiben
6
   while(SPI2STATbits.SPIRBE);     // Warten wenn Empfangspuffer leer
7
   return SPI2BUF;                 // Lesen
8
}
9
:
10
void RTCread(void)              // Datum und Uhrzeit aus RTC lesen
11
{
12
  SS_RTC_Set();                 //   Chip select RTC active high!!
13
  SPI2TxRx (RTC_READ | CLOCK_PAGE | SECONDS); // Returnwert uninteressant
14
  cnt_sec    = BCD2BYTE (SPI2TxRx(0));
15
  cnt_minute = BCD2BYTE (SPI2TxRx(0));
16
  cnt_hour   = BCD2BYTE (SPI2TxRx(0));
17
  cnt_date   = BCD2BYTE (SPI2TxRx(0));
18
  cnt_day    = BCD2BYTE (SPI2TxRx(0));
19
  cnt_month  = BCD2BYTE (SPI2TxRx(0));
20
  cnt_year   = BCD2BYTE (SPI2TxRx(0)) + 2000;
21
  SS_RTC_Clear();     //   Chip select RTC active high!!
22
}
23
:

Ich würde diese Zeitinformationen auch nicht in zig globale Variabeln cnt_xxx packen, sondern in 1 einzigen Struct, und einen Pointer darauf der Funktion RTCread() als Parameter übergeben.

OP #7401557
Lesenswert?

Peter D. schrieb:

Da beides eh nie getrennt ausgeführt werden kann und darf, würde ich daraus eine Funktion machen.

Das sehe ich anderes. Beim RTC OK, aber es wird eine zeitkritische SPI Abfrage geben. Daher in 2 Funktionen aufgeteilt, die dann in einer State-Maschine aufgerufen werden.

bei 1 MHZ SPI Takt dauert ja die Zeit zwischen senden/Empfangen immerhin
8 us.

Moderator (Firma: Titel) Persönliche Seite #7401568
Lesenswert?

Dirk F. schrieb:

Das sehe ich anderes. Beim RTC OK, aber es wird eine zeitkritische SPI Abfrage geben. Daher in 2 Funktionen aufgeteilt, die dann in einer State-Maschine aufgerufen werden.

Das kann dann zu lustigen Effekten führen, denn während des kompletten Lesevorgangs ist der SPI ja blockiert, weil der CE aktiv gehalten werden muss. Du musst also die Hardware für andere Teilnehmer, die den SPI i ihrers FSM ebenfalls verwenden möchten, sperren, damit die nicht ihren SS# auch noch aktivieren und ihre Daten gleich mal hinterherschicken...

Daher in 2 Funktionen aufgeteilt

Dann solltst du aber angesichts des 16 Worte tiefen Sendepuffers auch nicht warten, bis der Sendepuffer leer ist, sondern du kannst alle 8 Bytes sofort hintereinander senden und dann die Daten nach und nach abholen.

1
void SPI2Put (unsigned char wert)
2
{
3
   SPI2BUF = wert;              // Schreiben
4
}
5
unsigned char SPI2Get (void)
6
{
7
   while(SPI2STATbits.SPIRBE);  // Warten wenn Empfangspuffer leer
8
   return SPI2BUF;
9
}
10
void RTCread(void)              // Datum und Uhrzeit aus RTC lesen
11
{
12
  SS_RTC_Set();                 //   Chip select RTC active high!!
13

14
  // TX
15
  SPI2Put (RTC_READ | CLOCK_PAGE | SECONDS);
16
  for (int i=0; i<7; i++) SPI2Put (0);
17

18
  // RX
19
  SPI2Get(); // Skip first byte
20
  cnt_sec    = BCD2BYTE (SPI2Get());
21
  cnt_minute = BCD2BYTE (SPI2Get());
22
  cnt_hour   = BCD2BYTE (SPI2Get());
23
  cnt_date   = BCD2BYTE (SPI2Get());
24
  cnt_day    = BCD2BYTE (SPI2Get());
25
  cnt_month  = BCD2BYTE (SPI2Get());
26
  cnt_year   = BCD2BYTE (SPI2Get()) + 2000;
27

28
  SS_RTC_Clear();     //   Chip select RTC active high!!
29
}
OP #7401587
Lesenswert?

Lothar M. schrieb:

Das kann dann zu lustigen Effekten führen, denn während des kompletten Lesevorgangs ist der SPI ja blockiert, weil der CE aktiv gehalten werden muss. Du musst also die Hardware für andere Teilnehmer, die den SPI i ihrers FSM ebenfalls verwenden möchten, sperren, damit die nicht ihren SS# auch noch aktivieren und ihre Daten gleich mal hinterherschicken...

Stimmt. Aber bei meiner zeitkritische SPI Anwendung wird über SPI4 an nur ein Teilnehmer verbunden.

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren