Forum: Mikrocontroller und Digitale Elektronik Real Time Clock RV3149-C3 Fehlverhalten ?


von Dirk F. (dirkf)


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
    }

von Peter D. (peda)


Lesenswert?

Dirk F. schrieb:
> Aber nur mit dem zusätzlichen Schreiben/Lesen eines Bytes zwischen Byte
> Befehl und Byte Daten Sekunden.

Magst Du uns auch den MC nennen?
Vielleicht hat der ja noch Bytes vom vorherigen Befehl im 
Empfangspuffer.
Typisch gibt es ein Flag, was angibt, ob der Puffer auch leer ist.

von Dirk F. (dirkf)


Lesenswert?

Peter D. schrieb:
> Magst Du uns auch den MC nennen?

PIC32MZ0512EFF100

von Dirk B. (garag)


Lesenswert?

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

von Peter D. (peda)


Lesenswert?

Dirk F. schrieb:
> PIC32MZ0512EFF100

Sehe da nur endlos "Loading".
Schau mal selber unter SPI nach, wie man den Empfangspuffer leert.

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


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?
- 
https://www.microcrystal.com/fileadmin/Media/Products/RTC/App.Manual/RV-3149-C3_App-Manual.pdf

von Dirk F. (dirkf)


Lesenswert?

Lothar M. schrieb:
> Besonders SPI2Get() ist interessant

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

von Dirk F. (dirkf)


Lesenswert?

Lothar M. schrieb:
> Und ganz interessant ist auch: was siehst du mit dem Oszi/LA auf den
> Busleitungen?

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

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


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

: Bearbeitet durch Moderator
von Andras H. (kyrk)


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;
}

von Dirk F. (dirkf)


Lesenswert?

Lothar M. schrieb:
> Vielleicht solltest du da vor dem Lesen warten, bis der
> Empfangspuffer voll ist.

Ich möchte ja immer nur 1 Byte übertragen und empfangen,
Der SPI Puffer hat wohl 16 Byte.
Da vermute ich, dass das Bit für Empfangspuffer voll nur gesetzt wird, 
wenn alle 16 Byte gefüllt sind... Oder ?

von Peter D. (peda)


Lesenswert?

Andras H. schrieb:
> while(!SPI2STATbits.SPIRXIF);     // Warten bis Sendepuffer leer

Ein SPIRXIF kann ich nicht finden, nur ein SPIRBF.

Register 23-3:SPIxSTAT: SPI Status Register (Continued)
bit 5
SPIRBE: RX FIFO Empty bit (valid only when ENHBUF = 1)
1 = RX FIFO is empty (CRPTR = SWPTR)
0 = RX FIFO is not empty (CRPTR < SWPTR)

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


Angehängte Dateien:

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

von Dirk F. (dirkf)


Lesenswert?

Andras H. schrieb:
> _sync();
Was macht denn eigentlich dieser Befehl ?

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


Angehängte Dateien:

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.

: Bearbeitet durch Moderator
von Dirk F. (dirkf)


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
    }

von Dirk F. (dirkf)


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
    }

von Peter D. (peda)


Lesenswert?

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

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


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.

: Bearbeitet durch Moderator
von Dirk F. (dirkf)


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.

von Dirk F. (dirkf)


Lesenswert?

Lothar M. schrieb:
> sondern in 1 einzigen Struct,

Ja, gebe ich Dir Recht.

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


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
}

: Bearbeitet durch Moderator
von Peter D. (peda)


Lesenswert?

Lothar M. schrieb:
> Das kann dann zu lustigen Effekten führen

Ja, wenn man nicht zu jedem Sendebyte auch immer ein Empfangsbyte 
abholt.
Und will man mal ein Byte zuviel empfangen, hängt die CPU.
Man muß also genau mitzählen.
Man sollte daher eine static Zählvariable mitlaufen lassen.

von Dirk F. (dirkf)


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.

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.