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

#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

Re: Temperaturmessung mit PT100 und MAX31865

#26

Ungelesener Beitrag Nico » 18. Apr 2019, 07:09

Wobei mir jetzt noch eingefallen bzw. aufgefallen ist, wenn du die Temperaturberechnung wirklich auf dem PIC machen möchtest, ist mein Vorschlag keine gute Idee:

Code: Alles auswählen

temp = (adccode >> 6) - 256;
temp ist ja vom Typ ein double und da sind die Bit-Shift Operanden Unsinn. Es müsste dann eher so aussehen (der Cast auf double ist ggf. nicht notwendig, wenn du wirklich 32.0 statt 32 schreibst):

Code: Alles auswählen

temp = (double)(adccode / 32.0) - 256;
Sorry, arbeite i.d.R. eher selten mit Floating-Point Typen.

Gruß Nico

Re: Temperaturmessung mit PT100 und MAX31865

#27

Ungelesener Beitrag emy » 18. Apr 2019, 10:53

Der MAX31865 antwortet! :)

Mit Hilfe eines brakpoints lasse ich mir die Variable "temp" anzeigen. Derzeit noch ohne das Auffangen des Faultbits, was du davor vorgeschlagen hast (finde ich aber gut und möchte ich auch umsetzen :) ). Es werden mir in "temp" 4 Bytes ausgegeben: 11000011, 10000000, 00000000, 00000000.

Aktuell verwende ich diesen Code:

Code: Alles auswählen

void spi_transfer (uint8_t *pWr, uint8_t *pRd, uint8_t len){
           uint8_t i; 
           
           clock = 0;
           
           if (pWr == NULL) return;
           
           while (len){
               for (i=0; i<8; i++){
                    SDO = ((*pWr) & (0x80 >> i)) ? 1 : 0;
                   __delay32(100);
                   clock ^= 1;
                   
                   if (pRd) {
                       if (SDI){
                           *pRd |= (0x80 >> i);
                       }
                       else {
                           *pRd &= ~(0x80 >> i);
                       }
                      
                   }
                __delay32(100);
                clock ^=1;    
               }
               pWr++;
               if (pRd) pRd++;
               
               len--;
           }
           clock = 1;
        
        }
        

Code: Alles auswählen

void maxRTD_request_new_meas (void)
    {
        uint8_t buf[8];
        
        buf[0] = 0x80;
        buf[1] = 0xC2;
        
        cs_max = 0;
        spi_transfer(buf, NULL, 2);
        cs_max = 1;
    }

    uint16_t maxRTD_read_res (void)
    {
        uint8_t buf[8];
        
        buf[0] = 0x01;
        buf[1] = 0x00;
        buf[2] = 0x00;
        
        cs_max = 0;
        spi_transfer(buf, buf, 3);
        cs_max = 1;
        
        return ((buf[1] << 8) | buf[2]);
    }

Code: Alles auswählen

int main(void)
{
    // initialize the device
    SYSTEM_Initialize();
    
    uint16_t adccode;
    double temp;
     
    while (1)
    {
        maxRTD_request_new_meas();
        adccode = maxRTD_read_res();
        temp =(double)(adccode / 32.0) - 256;

        int i = i+1;
    }

    return 1;
}
(i für den brakpoint)

Warum werden hier jetzt 4 Bytes ausgegeben? Oder sind die letzten beiden, die aus nur Nullen bestehen, "unwichtig"?

Re: Temperaturmessung mit PT100 und MAX31865

#28

Ungelesener Beitrag Nico » 18. Apr 2019, 11:52

Ein paar allgemeine Anmerkungen:
  • Du verwendet in deinem Code clock vermutlich als Ersatz für den GPIO, der den SPI Clock erzeugen soll? Es ist allgemein üblich Defines ausschließlich in Großbuchstaben zu schreiben. Also würde ich dir empfehlen clock durch CLOCK oder besser noch durch (etwas aussagekräftiger) SCLK (normale Bezeichnung für den SPI Clock) zu ersetzen.
  • Sofern du weißt bzw. es in deinem Code sauber dokumentiert ist, was __delay32 macht, dann okay. Grundsätzlich immer drauf achten, dass Namen, egal ob für Funktionen, Variablen oder sonstiges, aussagekräftig sind ;-)
  • Arbeite mit Kommentaren im Code. Das heißt nicht, dass man folgende Zeile a++; mit einem Kommentar // a inkrementieren versieht sondern vielmehr den Sinn/Zweck verdeutlicht. Du wirst sehen dass solche Kommentare Gold wert sind, wenn du mal etwas Zeit ins Land gehen lässt und zurück zu deinem Code kommst :-) Wie gesagt ein gesundes Maß an Kommentaren, nicht jede (offensichtliche) Zeile muss erklärt werden.
  • Besonders auf Mikrocontrollern, wo der Speicher nicht in Hülle und Fülle vorhanden ist, sollte man als Programmierer darauf achten nicht zu verschwenderisch zu sein. Also wenn ein Buffer lediglich zwei Werte fassen wird, mach ihn auch nur so groß.
Jetzt zu deiner Frage:
Warum werden hier jetzt 4 Bytes ausgegeben?
Das liegt an dem Datentyp double. Bitte schau mal in MPLABX nach: Öffne mal deine Project Properties (siehe links das Fenster Project - hier einen Rechtsklick auf den Projektnamen, dann Project-Properties) und geh dann auf Conf > XC8 global options > XC8 linker. Wähle dann rechts im Fenster "Memory Model" im Auswahlfeld aus und schau nach was bei Size of Double steht (24 oder 32 bit), siehe:

mplabx_memory_model_double.png

Es macht aber auch im Allgemeinen wenig Sinn sich eine Floating-Point Variable (also float oder double) im Binärformat anzusehen. Es sei denn man möchte wirklich das Format dieser lernen/verstehen. Im MPLAB kannst du dir den Wert von der Variable auch als Dezimalzahl anzeigen lassen, so dass du dann auch direkt die gemessene Temperatur sehen können solltest.

In deinem Code fehlt nun allerdings das Rechtsverschieben um eine Stelle (das Fault Bit gehört nicht zum eigentlichen ADC Wert). So wird die errechnete Temperatur nicht passen. Also entweder so realisieren, wie von mir zuvor vorgeschlagen oder das Fault Bit unter den Tisch fallen lassen, siehe deine Zeile (muss angepasst werden):

Code: Alles auswählen

return ((buf[1] << 8) | buf[2]);
Noch etwas: Das hier (in deiner while Schleife) macht keinen Sinn:

Code: Alles auswählen

int i = i+1;
Verschiebe das Erzeugen von i an den Beginn der main Funktion (mit Initialisierung auf 0) und schreib das Inkrementieren (i++;) dann in die Schleife.

Gruß Nico

Re: Temperaturmessung mit PT100 und MAX31865

#29

Ungelesener Beitrag emy » 18. Apr 2019, 12:39

Also würde ich dir empfehlen clock durch CLOCK oder besser noch durch (etwas aussagekräftiger) SCLK (normale Bezeichnung für den SPI Clock) zu ersetzen

Gemacht:)
Sofern du weißt bzw. es in deinem Code sauber dokumentiert ist, was __delay32 macht, dann okay
Die Funktion __delay_ms(100); und alle anderen möglichen Variationen funktioneiren nicht. Ich habe die __delay32 Funktion im Compiler-Handbuch gefunden...
Arbeite mit Kommentaren im Code
Die folgen auf jeden Fall noch, gerade sehr wichtig, wenn es in einer Thesis beschrieben wird.

Code: Alles auswählen

return ((buf[1] << 7) | (buf[2] >> 1));
Das ist jetzt der Returnwert, hatte ich vergessen das stimmt. Ich verwende den XC16 und dort ist die Einstellung für Size of Double nicht möglich. Habe jetzt auch ein bisschen herumprobiert, aber es kommen keine sinnigen Werte für die Temperatur zu stande. Als Dezimalzahl anzeigen lassen, hatte ich bereits, da das aber keinen Sinn ergab, wollte ich mir das ganze mal in binär angucken.

Hab jetzt spaßeshalber temp als Integer definiert (sind ja nur 16 Bit). Dann steht in temp immer die letzte Zahl der Zeile

Code: Alles auswählen

temp =(adccode / 32) - 256;
In diesem Fall jetzt 256, wenn ich die Zahl ändere, ändert sich auch temp zu dieser Zahl. Da stimmt dann ja irgendetwas nicht...

Re: Temperaturmessung mit PT100 und MAX31865

#30

Ungelesener Beitrag Nico » 18. Apr 2019, 13:24

Stimmt, du arbeitest ja gar nicht mit dem XC8, mein Fehler.

Also temp musst du auf jedenfall als double lassen. Hast du dir nach dem Auslesen des MAX mal die 15 Bit angesehen? Ist da evtl. das Fault Bit gesetzt?

Und kannst du vielleicht mal einen Screenshot von der Kommunikation zum MAX zeigen? Idealerweise die Config sowie das Auslesen.

Gruß Nico

Antworten

Wer ist online?

Mitglieder in diesem Forum: 0 Mitglieder und 1 Gast