Temperaturmessung mit PT100 und MAX31865

In dieses Forum gehören all deine Fragen rund um die Elektrotechnik und im Speziellen zum Bereich der Mikrocontrollertechnik.

Re: Temperaturmessung mit PT100 und MAX31865

#11

Ungelesener Beitrag emy » 2. Apr 2019, 21:38

Ich danke dir jetzt schon einmal für deine Hilfe und Bemühungen. Ich werde mich da einmal durchhangeln und werde schon einmal meine ersten Versuche starten.

Bestimmt komme ich noch einmal drauf zurück, wenn ich vor konkreteren Problemen stehe.

Vielen Dank und liebe Grüße!

Re: Temperaturmessung mit PT100 und MAX31865

#12

Ungelesener Beitrag emy » 10. Apr 2019, 15:44

Ich nochmal...

Ich bin in der Lage das korrekte Byte zu dem korrekten Pin zu schreiben. Ein kleiner Fortschritt :)

Jetzt geht es darum, aus dem MSB und LSB Register zu lesen. In diesen Registern wird soweit ich das verstanden habe, das Verhältnis vom RTD Widerstand und dem Referenzwiderstand angegeben. Nun habe ich das kleine Verständnisproblem vom Einlesen. Es ist ja wahrscheinlich so, dass man die beiden Bytes bitweise von links nach rechts, angefangen mit dem MSB Register, einliest oder? Dieser ADC-Code wird dann für die Berechnung vom Widerstand verwendet: R_RTD=(ADC-Code * R_Ref)/2^15. Muss so dann die Temperatur extra berechnet werden? Mit der Callendar-Van Dusen Gleichung? Problem derzeit ist gerade eigentlich das Einlesen der beiden Bytes... Zuerst MSB Register Adresse, dann bitweises einlesen und dann LSB Regsiter Adresse und das gleiche noch einmal?

Danke schon einmal :)
Viele Grüße

Re: Temperaturmessung mit PT100 und MAX31865

#13

Ungelesener Beitrag Nico » 11. Apr 2019, 13:06

[..] Es ist ja wahrscheinlich so, dass man die beiden Bytes bitweise von links nach rechts [..] einliest oder? [..]
Ja, siehe auch "Figure 1. Timing Diagram: SPI Read Data Transfer" im Datenblatt des MAX31865.
[..] angefangen mit dem MSB Register [..]
Wenn du beginnend mit der Adresse des MSB zwei Bytes liest, sprich 01h als Kommando nutzt und dann zwei Bytes liest, dann wirst du zuerst das MSB Register und anschließend das LSB Register lesen, ja.
[..] Dieser ADC-Code wird dann für die Berechnung vom Widerstand verwendet: R_RTD=(ADC-Code * R_Ref)/2^15. Muss so dann die Temperatur extra berechnet werden? [..]
Ja die Temperatur muss berechnet werden, Den Widerstandswert hast du ja bereits. Nicht vergessen den ADC-Wert um eine Stelle nach rechts zu shiften um das fault Bit zu streichen.
[..] Mit der Callendar-Van Dusen Gleichung? [..]
Entweder so oder schneller allerdings deutlich speicherintensiver mit Hilfe einer LUT (look up table).
[..] Problem derzeit ist gerade eigentlich das Einlesen der beiden Bytes... Zuerst MSB Register Adresse, dann bitweises einlesen und dann LSB Regsiter Adresse und das gleiche noch einmal? [..]
Das Vorgehen hierzu ist im Datenblatt auf Seite 5 in Figure 1. Timing Diagram: SPI Read Data Transfer dargestellt. Zum Lesen eines Registers aktivierst du den Chip zunächst mit seinem Chip Select signal (low aktiv) und sendest dann die Adresse des Registers bei dem du mit dem Lesen beginnen möchtest. Wichtig beim Senden der Register-Adresse das Bit 7 nicht setzen, da der Chip sonst einen Schreib-Packet erwartet (siehe Table 1. Register Addresses and POR State).

Direkt im Anschluss (selber CS low Zyklus), kannst du "beliebig" viele Register hintereinander weg lesen (du liest also zwei mal, denn du möchtest ja RTD MSBs und RTD LSBs haben).

Gruß Nico

Re: Temperaturmessung mit PT100 und MAX31865

#14

Ungelesener Beitrag pic18 » 11. Apr 2019, 20:55

Callendar-Van Dusen Gleichung
Ich habe die ganze Zeit gerätselt. Wie man den Widerstandswert nach Temperaturwert umrechnet. Da es keine Gerade ist. Jetzt bin ich ein Stück schlauer.
Ich frage mich gerade wie man so eine komplexe Formel mit einem Pic berechnen kann?
https://de-de.wika.de/upload/DS_IN0029_de_de_59666.pdf

Re: Temperaturmessung mit PT100 und MAX31865

#15

Ungelesener Beitrag Nico » 12. Apr 2019, 06:54

Es kommt darauf an wie genau man die Temperatur braucht. Wenn man beispielsweise mit -1,75°C Ungenauigkeit bei -100°C und -1,4°C bei +100°C leben kann, wird es mit der folgenden Gleichung (siehe Linearizing Temperature Data im Datenblatt) deutlich einfacher:

Code: Alles auswählen

Temperature (°C) = (ADC code / 32) - 256
Ansonsten gibt es hier eine schöne Application note, die sich genau mit diesem Thema auseinander setzt. Die AN trennt den Temperaturbereich in >=0°C und <0°C. Die mathematische Betrachtung im Bereich >=0°C ist vergleichsweise einfach:

Code: Alles auswählen

T_rtd(r) = [ Z1 + sqrt(Z2+Z3*r) ] / Z4
Hierbei sind die Z Faktoren jeweils Konstanten (siehe Application Note). Wenn das Ergebnis ergibt, dass die Temperatur <0°C ist, muss mit einer Polynom 3. Grades approximiert werden (auch in der AN gezeigt).

Wie gesagt letztendlich hat man folgende Möglichkeiten:
  • Approximation mit Polynom 2. bzw. 3. Grades
  • Tabelle (LUT)
  • Approximation mit Hilfe einer Geraden
Version (1) ist nicht 100% genau aber schon nah dran. Sie braucht relativ wenig speicher, geht aber auf Kosten der Komplexität. Bei Version (2) dreht sich de Spieß. Je nachdem wie fein man die Schrittweite gliedert wird natürlich der Speicherbedarf sehr groß. Dafür ist die Variante jedoch unschlagbar schnell und zudem auch sehr genau. Ein Kompromiss zwischen Schnelligkeit und Genauigkeit liefert Version (3). Diese Variante ist natürlich längst nicht so genau wie (1) oder (2), dafür jedoch deutlich schneller als (1) und gleichzeitig wesentlich schonender in Bezug auf Speicher als (3).

Wie man sieht hat man also mal wieder die Qual der Wahl.

PS: Eine Umsetzung der in der Application Note getrennten Betrachtung (<0°C oder >=0°C) findest du als C-Beispielcode hier.

Viele Grüße
Nico

Re: Temperaturmessung mit PT100 und MAX31865

#16

Ungelesener Beitrag emy » 12. Apr 2019, 10:28

Sehr gute Erklärung, danke!
Ich hoffe ich habe das jetzt alls richtig verstanden...
Für den Datentransfer setze ich nun die Pins auf entweder 0 oder 1, also ganz einfach (ein Muss für mein Thesisbetreuer):

Code: Alles auswählen

uint8_t SPI_transfer (uint8_t D){     // D ist das zu übergebende Byte
            uint8_t i;
            uint8_t Bit;
            clock = 0;   
            
            for (i=0; i<8; i++) {
                Bit = D >> (7-i);
                SDO = Bit;    
                clock = 1;
                clock = 0;
            }
            return Bit;
        }
Für das Schreiben des Configurationsregisters beispielsweise benutze ich diesen Code:

Code: Alles auswählen

void write (uint8_t w_addr, uint8_t data) {
     cs_max = 0;      // CS low
     SPI_transfer(w_addr);
     SPI_transfer(data);
     cs_max = 1;      // CS high
 }
Mit Hilfe des Oszis kann ich das auch genau überprüfen...
Jetzt kommt das für mich etwas knifflige, das Lesen der beiden Register. Ich hoffe ich habe jetzt das jetzt so einigermaßen richtig umgesetzt.

Code: Alles auswählen

uint8_t read (uint8_t r_addr){        // statt void uint8_t???
     cs_max = 0;      // CS low
     SPI_transfer(r_addr);
     uint8_t value = SPI_transfer(0x00); // dummy write to provide SPI Clock Signals to read
     cs_max = 1;      // CS high
     return value;
 }

Code: Alles auswählen

uint8_t get_temp() {       // void statt uint8_t
 uint8_t lsb_rtd = read(rtd_lsb);
     uint8_t msb_rtd = read(rtd_msb);
     float rtd_value = ((msb_rtd << 8) + lsb_rtd) >> 1; 
     
     double temp = (rtd_value / 32) - 256;
     return temp;
}
Ich hoffe, dass es nun so langsam Gestalt annimmt :D Vielleicht kannst du noch einmal einen kurzen Blick riskieren.

Re: Temperaturmessung mit PT100 und MAX31865

#17

Ungelesener Beitrag Nico » 12. Apr 2019, 19:08

Ohje das sieht ja so aus, als würdest du dein SPI mit Bitbanging realisieren? Das würde ich dir nicht empfehlen. Für solche Aufgaben haben Mikrocontroller eigene Hardware-Einheiten, die einem diese Arbeit abnehmen. Man muss lediglich das Interface initialisieren und kann dann über einfaches schreiben auf einen Buffer direkt den Transfer anstoßen ohne selber einzugreifen. Das gleichzeitig gelesene Byte wird automatisch in einen Eingangspuffer geschrieben und kann dann im Anschluss von dir ausgelesen werden, fertig. Schau mal bei 15.0 SERIAL PERIPHERAL INTERFACE (SPI) im Datenblatt nach.

Gruß Nico

Re: Temperaturmessung mit PT100 und MAX31865

#18

Ungelesener Beitrag emy » 14. Apr 2019, 16:08

Von meinem Betreuer ist es so gewünscht, damit es nachher leichter nachzuvollziehen ist. Oder ist es damit nicht möglich? Ich dachte ich bin meinem Ziel schon ein bisschen näher, da langsam die Zeit etwas fehlt 🙈
Oder sind meine Funktionen nur Blödsinn?

Re: Temperaturmessung mit PT100 und MAX31865

#19

Ungelesener Beitrag Nico » 14. Apr 2019, 18:45

Von meinem Betreuer ist es so gewünscht, damit es nachher leichter nachzuvollziehen ist
Also das kann ich nicht nachvollziehen, aber gut, sei es drum.
Oder ist es damit nicht möglich?
Doch sicher ist das möglich. Bei deiner gezeigten Funktion (SPI_transfer()) fehlt mir allerdings das Einlesen des vom Gegenüber gesendeten Bytes auf SDI sowie ein entsprechendes Timing. Hier mal ein Vorschlag, wie es aussehen könnte (ungetestet):

Code: Alles auswählen

#define SCLK	LATFbits.LF3
#define SDO	LATDbits.LD2
#define SDI 	PORTDbits.RD1

void spi_transfer (uint8_t *pWr, uint8_t *pRd, uint8_t len)
{
   uint8_t i;
	
   // pull down the clock level
   SCLK = 0;
	
   // return if send data is not defined
   if(pWr == NULL) return;
	
   while(len)
   {
      for(i=0; i<8; i++)
      {
         // send bits from MSB to LSB
         SDO = (*pWr) & (0x80 >> i);
         // <-- delay at least 100ns
         // clock toggle
         SCLK ^= 1;
         
         // read from MSB to LSB (if read pointer is not NULL)
         if(pRd)
         {
            if(SDI)
            {
               *pRd |= (0x80 >> i);
            }
            else
            {
               *pRd &= ~(0x80 >> i);
            }
         }
         
         // <-- delay at least 100ns
         SCLK ^= 1;
      }

      pWr++;
      if(pRd) pRd++;
      len--;
   }
   
   // clock level idle high
   SCLK = 1;
}
Die Übergabeparameter pWr und pRd sind jeweils Pointer auf uint8_t. In diesem Fall wären es entsprechend Zeiger auf Arrays, die im Fall von pWr die Bytes enthalten, die gesendet werden sollen. Dementsprechend ist der Pointer auf pRd ein Pointer auf ein Array, das als Empfangspuffer für die zeitgleich gelesenen Bytes dient. Dieser Pointer kann ein Nullpointer (NULL) sein, wenn keine Bytes gelesen werden sollen. Mit len wird der Funktion mitgeteilt, wie viele Bytes geschrieben bzw. gelesen werden sollen.

Wichtig: Die GPIOs müssen korrekt initialisiert sein, sprich SDO und SCLK als Ausgang und SDI als Eingang (digital). Des Weiteren wichtig ist es, dass an den Stellen, wo ich es als Kommentar angedeutet habe mindest Wartezeiten/Pausen vorhanden sein müssen, da der MAX31865 "nur" eine Taktfrequenz von maximal 5MHz unterstützt.

Und bitte auch beachten, der Code ist nur so daher geschrieben. Ich habe weder geprüft ob er kompilierbar ist geschweige denn ob er funktioniert ;-)

Ich würde dir sehr empfehlen das Senden/Lesen mit einem Scope oder Logic-Analyzer zu kontrollieren. Wenn das dann fehlerfrei klappt, ist der Rest nicht mehr so kompliziert :-) Es macht jedoch keinen großen Sinn mit der weiteren Programmierung fortzufahren, wenn man nicht sicher weiß ob die grundlegende Kommunikation hinhaut.

Gruß Nico

Re: Temperaturmessung mit PT100 und MAX31865

#20

Ungelesener Beitrag emy » 15. Apr 2019, 15:49

Das hat mir schon einmal sehr weitergeholfen! Allerdings habe ich irgendwie ein Problem mit den Pointern bzw. funktioniert das nicht wie es soll. Ich habe es daher für mich ein bisschen geändert, sodass alleine für das Schreiben folgendes entstanden ist:

Code: Alles auswählen

uint8_t SPI_transfer (uint8_t D, uint8_t read, uint8_t len){     // D ist das zu übergebende Byte
            uint8_t i;
            clock = 0;
            
            while (len){
               for (i=0; i<8; i++) {
                _RE4 = D >> i;
                __delay32(100);
                clock ^= 1;
                
                if (read) {
                       if (SDI){
                           read |= (0x80 >> i);?
                       }
                       else {
                           read &= ~(0x80 >> i);?
                       }
                      
                   }
                
                
                __delay32(100);
                clock ^=1;  
               
            }
             
            len--;   
            }
            clock = 1;
            
            return D;
        }
Mit dem Aufruf :

Code: Alles auswählen

	cs_max = 0;
        SPI_transfer(configuration_write, NULL, 1);
        SPI_transfer(max31865_config, NULL, 1);
        cs_max = 1;
kann ich jetzt die Adresse und die Konfiguration senden, funktioniert auch :)

Genau das gleiche möchte ich nun für das Lesen machen, wobei ich mich dabei noch ziemlich schwer tu, selbst das gedanklich durchzugehen...
  • Hierbei wird ja CS ebenfalls auf 0 gesetzt.
  • Darauf folgt das Senden der MSB Adresse.
  • Das Byte, was an SDI anliegt, wird einer Variablen übergeben.
  • Darauf folgt das Senden der LSB Adresse.
  • Das Byte, was an SDI anliegt, wird einer Variablen übergeben.
  • CS wird wieder hoch gesetzt.

Code: Alles auswählen

	uint16_t value;
        cs_max = 0;
        SPI_transfer(rtd_msb, ?, 1);
        value=...
        SPI_transfer(rtd_lsb, ?, 1);
        value=...
        cs_max = 1;
Danach können die Bytes ja um 1 nach rechts geshiftet werden und schließlich wird die Temperatur berechnet und in einer Variablen ausgegeben. Ist das vom logischen Ablauf in etwa richtig? Ist es überhaupt möglich, ohne Pointer einzulesen wie es beim Schreiben bei mir funktioniert?

Code: Alles auswählen

_RE4 = D >> i;
An diesem Pin habe ich mir das auf dem Oszi anzeigen lassen, sonst ist es natürlich SDO.


Beiträge zusammengeführt - Nico

Re: Temperaturmessung mit PT100 und MAX31865

#21

Ungelesener Beitrag Nico » 15. Apr 2019, 18:18

Ich würde dir die Umsetzung schon mit dem Pointer Ansatz empfehlen:

Code: Alles auswählen

#define SCLK	LATFbits.LF3
#define SDO	LATDbits.LD2
#define SDI 	PORTDbits.RD1

void spi_transfer (uint8_t *pWr, uint8_t *pRd, uint8_t len)
{
   uint8_t i;
	
   // pull down the clock level
   SCLK = 0;
	
   // return if send data is not defined
   if(pWr == NULL) return;
	
   while(len)
   {
      for(i=0; i<8; i++)
      {
         // send bits from MSB to LSB
         SDO = (*pWr) & (0x80 >> i);
         // <-- delay at least 100ns
         // clock toggle
         SCLK ^= 1;
         
         // read from MSB to LSB (if read pointer is not NULL)
         if(pRd)
         {
            if(SDI)
            {
               *pRd |= (0x80 >> i);
            }
            else
            {
               *pRd &= ~(0x80 >> i);
            }
         }
         
         // <-- delay at least 100ns
         SCLK ^= 1;
      }

      pWr++;
      if(pRd) pRd++;
      len--;
   }
   
   // clock level idle high
   SCLK = 1;
}
Nutzen kannst du die Funktion dann so:

Code: Alles auswählen

uint8_t buf [8];

// ...

// write two bytes without read
buf [0] = 0x80;
buf [1] = ....;
cs = low;
spi_transfer(buf , NULL, 2);
cs = high;

// ...

// read two bytes starting from <RTD MSBs>
buf[0] = 0x01;
buf[1] = 0x00; // nicht zwingend notwendig aber sauberer zum Messen =)
buf[2] = 0x00; // nicht zwingend notwendig aber sauberer zum Messen =)
cs = low;
spi_transfer(buf , buf , 1+2);
cs = high;
Die 1+2 habe ich lediglich so geschrieben, damit der Zusammenhang etwas offensichtlicher wird. Du durchläufst zum Lesen von (hier als Beispiel zwei Bytes) in Summe drei Zyklen: 1. Das Senden der Adresse von der an beginnend du im Anschluss lesen möchtest (im Beispiel 0x01, wie im Datenblatt als Lese-Adresse für RTD MSBs angegeben). 2. und 3. sind die zwei Zyklen zum Lesen von RTD MSBs (hier beginnt das Lesen) und von RTD LSBs (da es direkt nach der Adresse für RTD MSBs liegt).

Vielleicht ist dir aufgefallen, dass beim Aufruf von spi_transfer() der Übergabeparameter für den Sende- und den Lesebuffer beides mal die Startadresse desselben Arrays genutzt wurde:

Code: Alles auswählen

spi_transfer(buf , buf , 1+2);
Der Funktion ist das egal, denn sie arbeitet sich Byte für Byte durch die einzelnen Bits durch und zwar beginnend beim MSB. Nehmen wir spaßeshalber mal an, dass in buf an der ersten Stelle des Arrays, also in buf[0], der Wert 0xAA steht (willkürlich gewählt) und wir auch lediglich einen einzigen Zyklus (len=1) durchlaufen.

Was würde passieren?

Die Funktion würde immer zum Senden der Bits auf die selbe Variable greifen, wie beim Einlesen der Bits und zwar in der Reihenfolge:
  • Senden Bit i
  • Einlesen Bit i
  • i um eins hochzählen
  • Solange wiederholen, bis i>7 (also 8 mal)
Nehmen wir außerdem an, dass ein IC, an das wir die 0xAA senden zeitgleich das Byte 0x55 sendet. Also während wir 0xAA auf SDO senden, empfangen wir 0x55 auf SDI. Der Ablauf wäre demnach wie folgt (vereinfacht ausgedrückt)

Senden (SDO): 0xAA = 0b10101010
Empfangen (SDI): 0x55 = 0x01010101
  • Senden Bit 7 [=1] auf SDO
  • Einlesen Bit 7 [=0] von SDI (dieses Bit wird nun in den Buffer geschrieben wo die 0xAA steht - das ist okay, da dessen Bit (7) ja gerade schon gesendet wurde und es somit nicht mehr gebraucht wird)
  • Fortfahren bis alle Bits gesendet/empfangen
Gruß Nico



Nachtrag: Nach dem Aufruf von spi_transfer(buf , buf , 1+2); stehen die zwei gelesenen Bytes in buf[1] (RTD MSBs) und buf[2] (RTD LSBs). Natürlich steht auch etwas in buf[0] aber da der MAX hier noch nichts gesendet hat (das war der Zyklus in dem wir dem MAX das Adressbyte gesendet haben), ist dieses Byte nicht von Interesse.

Re: Temperaturmessung mit PT100 und MAX31865

#22

Ungelesener Beitrag emy » 16. Apr 2019, 12:46

Danke für deine ausführliche Erklärung. Jetzt habe ich das mit dem Lesen auch verstanden :D

Ein Test für das Schreiben mit den Pointern funktioniert leider nicht so wie ich das möchte.

Code: Alles auswählen

buf [0] = 0x80;
Damit zeigt das Oszi nichts an, nur wenn dort 0x01 beispielsweise eingetragen ist, entstehen laufend Peaks. Bei Veränderung des Wertes, bleibt die Anzeige entweder genau gleich oder zeigt nichts an.

Mir ist noch nicht ganz klar, was die Zeilen

Code: Alles auswählen

SDO = (*pWr) & (0x80 >> i);
*pRd |= (0x80 >> i);
*pRd &= ~(0x80 >> i);
genau machen... Wenn du mir das noch kurz erklären könntest, damit ich evtl. meinen Fehler, warum die Adresse und das "Wort" nicht gesendet werden, finde …

Re: Temperaturmessung mit PT100 und MAX31865

#23

Ungelesener Beitrag Nico » 16. Apr 2019, 14:50

Versuch mal folgendes:

Diese Zeile:

Code: Alles auswählen

SDO = (*pWr) & (0x80 >> i);
Durch diese ersetzen:

Code: Alles auswählen

SDO = ((*pWr) & (0x80 >> i)) ? 1 : 0;
Zur Erklärung:

Code: Alles auswählen

SDO = (*pWr) & (0x80 >> i);
*pRd |= (0x80 >> i);
*pRd &= ~(0x80 >> i);
Zeile 1: Mit dem * sagst du, dass du nicht den Pointer selbst (also die Adresse) sondern den Wert haben möchtest, der an dieser Adresse steht. Also angenommen du hast vor dem Aufruf der SPI-Transfer Funktion einen Buffer mit uint8_t buf[8] angelegt und an die Funktion dann den Pointer buf übergeben, dann ist pWr ein Pointer auf den ersten Eintrag von buf also auf buf[0]. Der Einfachheit halber hier mal eine Veranschaulichung:

Code: Alles auswählen

Adresse    | Inhalt | Kommentar
-----------+--------+------------------
0x70000000 |  0xAA  | Hier liegt buf[0]
-----------+--------+------------------
    ...    |  ....  |
-----------+--------+------------------
0x80000000 |  0x00  | Das hier
-----------+--------+------------------
0x80000001 |  0x00  | ist der
-----------+--------+------------------
0x80000002 |  0x00  | Pointer
-----------+--------+------------------
0x80000003 |  0x70  | auf buf[0]
Also irgendwo im Speicher liegt dein Array buf mit seinen acht uint8_t Einträgen und an einer anderen Stelle liegt der Pointer, den du an die SPI Funktion übergeben hast. Bitte häng dich nicht an den Adressen auf, die sind bloß ausgedacht und machen unter Umständen keinen Sinn. Lediglich als Vereinfachung an dieser Stelle ;-)

Der Pointer muss entsprechend mehrere Speicherzellen belegen, da eine Adresse größer ist als bloß ein Byte - aber das ist hier jetzt gar nicht wichtig. Mir geht es darum, dass der Wert des Pointers (siehe Spalte Inhalt) ja erstmal gar nichts mit dem Wert der Variable zu tun hat, auf den er zeigt, also mit buf[0]. Damit wir nicht den Wert (das ist die Adresse) bekommen, sondern den Wert der Variable auf den der Pointe zeigt, nutzen wir den * Opertaor - siehe auch Tutorial.

Zurück zur Erklärung der Zeile:

Code: Alles auswählen

SDO = (*pWr) & (0x80 >> i);
Hier geschieht nichts weiter als, dass der Inhalt der Variable auf den der Pointer gerade zeigt mit bitweise und mit 0x80 >> i verglichen wird. Da i von 0 bis 7 in der for Schleife laufen wird, und somit die 0x80 immer weiter nach rechts verschiebt, wird also Bit für Bit auf eine 1 geprüft. Mit einfachen Worten: Im ersten Durchlauf der for und while Schleife: Ist Bit 7 von buf[0] eine 1? Wenn ja, setz SDO. Danach (2er Durchlauf der for aber immer noch erster Durchlauf der while Schleife: Ist Bit 6 von buf[0] eine 1? Wenn ja, setz SDO. Das wiederholt sich bis die while Schleife len mal durchlaufen wurde. Am Ende der while Schleife wird der Pointer stets um eins weiter gesetzt, so dass er (sofern über len vorgebeben) dann auf buf[1], buf[2] ... zeigt.

Die anderen beiden Zeilen (zum Einlesen):

Code: Alles auswählen

*pRd |= (0x80 >> i);
*pRd &= ~(0x80 >> i);
Da du jetzt bereits weißt, was es mit dem * auf sich hat, brauche ich darauf nicht mehr weiter eingehen. Hier wird also nun schreibend auf den Inhalt auf den der Pointer zeigt zugegriffen. Ich habe hier abgekürzte Schreibweisen verwendet, die man länglich auch so schreiben könnte:

Code: Alles auswählen

*pRd = (*pRd) | (0x80 >> i);
*pRd = (*pRd) & ~(0x80 >> i);
Im ersten Fall (also mit |) wird ein bitweises Oder ausgeführt. Da die Klammer vorrand hat, wird zunächst die 0x80 (also die 1 an Stelle von Bit 7) soweit nach rechts verschoben, wie i es angibt. Also wenn i=0 ist, dann bleibt der Klammerausdruck bei 0x80, wenn i=7 ist wird aus dem Klammerausdruck 0x01. Mit anderen Worten die erste Zeile setzt das i-te Bit (auf 1) in der Variable auf die pRd zeigt.

In der anderen Zeile passiert das Gegenteil: Der Klammerausdruck ist gleich bis auf das vorangestellte ~. Dieser Operator wird alle Bits umdrehen (aus 0 wird 1 und umgekehrt). Somit wird aus der Klammer wenn i=0 ist ein 0x7F bzw. ein 0xFE wenn i=7 ist. Mit anderen Worten: Hier wird das i-te Bit in der Variable auf die pRd zeigt gelöscht (auf 0 gesetzt).

Warum jetzt mein Gedanke die Zeile (siehe oben) auf diese zu ändern?

Code: Alles auswählen

SDO = ((*pWr) & (0x80 >> i)) ? 1 : 0;
Ohne den hinzugefügten Teil (? 1 : 0) würden verschiedene Werte ungleich 0 auf den GPIO geschrieben werden, wenn das jeweilige Bit eine 1 war. Ich bin mir aus dem Kopf gerade nicht ganz sicher ob das funktioniert. Du weißt, dass du mit folgender Zeile einen GPIO auf high setzen kannst:

Code: Alles auswählen

LATAbits.LA0 = 1;
Aber geht es auch so (und genau das war der Fall vor der Änderung)?

Code: Alles auswählen

LATAbits.LA0 = 2;
// oder
LATAbits.LA0 = 4;
// ...
Das ist jetzt bloß eine Idee. Wie gesagt ich hatte den Code nicht praktisch ausprobiert. Bitte versuch es mal mit der Änderung. Wenn es nicht klappt, simuliere ich es zu Hause mal durch.

Gruß Nico

Re: Temperaturmessung mit PT100 und MAX31865

#24

Ungelesener Beitrag emy » 17. Apr 2019, 16:43

Es funktioniert :) :) :) :) !!! Das was ich will, wird gesendet!

Die Funktion für das Lesen benötigt ja einen Returnwert, ist es in diesem Fall dann das 16 Bit lange Wort aus MSB und LSB, sprich buf[1] und buf[2]?
Dafür muss ja sicherlich der MSB Inhalt um 8 Stellen nach links geshiftet werden und dann mit einem ODER der LSB Inhalt drangehängt werden?
Ich dachte dann dabei an so etwas:

Code: Alles auswählen

return ((buf[1] << 8) | buf[2]);
buf[0] hat uns hier ja nicht zu interessieren. Wenn ich das dann mit

Code: Alles auswählen

adccode = maxRTD_read();
einer Variablen übergebe und die Temperatur mit

Code: Alles auswählen

double temp;
temp = (adccode / 32) - 256;
berechne, müsste dann doch die Temperatur (hier zur Zeit Zimmertemperatur) in der Variablen temp stehen oder?
Ach miste, in meiner Überlegung fehlt das Shiften um einen nach rechts des ADC-Codes.

Code: Alles auswählen

double temp;
temp = ((adccode >> 1) / 32) - 256;
lieber so vielleicht …

Re: Temperaturmessung mit PT100 und MAX31865

#25

Ungelesener Beitrag Nico » 17. Apr 2019, 18:00

Cool, dass die SPI Kommunikation jetzt schon mal klappt.

Ich würde an deiner Stelle nicht vollständig auf das Fault Bit des Sensors verzichten. Ansonsten würdest du ja nie mitbekommen ob eine Messung tatsächlich gültig oder fehlgeschlagen ist und somit verworfen werden sollte. Hier eine Möglichkeit dies mit aufzufangen:

Code: Alles auswählen

uint8_t maxRTD_read (uint16_t *pRTD)
{
	uint8_t fault;
	uint8_t buf[3];

	/*
	 * - ggf. anfordern der neuen Messung und warten auf DRDY
	 * - auslesen der Register MSB/LSB
	 */
	 
	 // ...
	 
	 // prüfen des FAULT bit
	 fault = buf[2] & 0x01;
	 
	 if(!fault) {
	 	*pRTD = (buf[1] << 7) | (buf[2] >> 1);
	 }

	return fault;
}
Der RTD Wert würde direkt in die Variable geschrieben auf die pRTD zeigt und ist nur dann gültig, wenn maxRTD_read() eine 0 zurück gibt.

Bezüglich der Berechnung des Temperaturwertes: Hier kannst du noch etwas optimieren ;-) Je nachdem wie dein Compiler konfiguriert ist, würde er das zwar auch selbstständig erledigen, wir wollen ja aber auch was lernen. Also aus dieser Zeile:

Code: Alles auswählen

temp = ((adccode >> 1) / 32) - 256;
Kannst du einfach diese machen:

Code: Alles auswählen

temp = (adccode >> 6) - 256;
Wie gesagt, werden diese Werte relativ ungenau sein (aufgrund der Approximation mit einer Geraden). An dieser Stelle eine Frage: Weshalb willst du im PIC überhaupt irgendeine Berechnung der Temperatur durchführen? An deiner Stelle würde ich den AD-Wert 1:1 nehmen und senden. Die Auswertung kann doch dann ganz bequem beim Empfänger durchgeführt werden (dann z.B. mit der Callendar Van Dusen Gleichung oder eben via Tabelle).

PS: Hast du an die notwendigen Wartezeiten in der spi_transfer() Funktion gedacht, damit der SPI Takt nicht zu schnell für den MAX wird?

Gruß Nico

Antworten