Forum: Mikrocontroller und Digitale Elektronik DS1820 an ATtiny25 in C, Massive Timingprobleme


von Erik (Gast)


Lesenswert?

Hallo zusammen,

ich versuche nun schon mehrere Wochen einen DS1820 per 1-wire an einen 
TINY25 zu koppeln, schaffe es jedoch nie eine Temperatur auszulesen.
Das ganze soll ein kleines Thermometer werden. Ich habe schon viel 
Erfahrung in der µC-Programmierung und einiges versucht. Das Problem 
sind wohl immer die Timings (wie eine Oszi-Messung bestätigt).

Habe schon folgendes gemacht (CLK: 8Mhz, interner Quarz):
- Timer verwendet (Vorteiler 16, Wartezeit/2, da nur 8-Bit-Timer)
- Timer verwendet (Vorteiler 8, wenn Wartezeit <255 sonst: Vorteiler 16, 
Wartezeit/2)
Beides mit und ohne Interrupt. Alles scheinbar zu ungenau.

- _delay_us
- _delay_loop
- _delay_loop_2 (lt. Doku braucht sie 4 Takte, also Wartezeit*2 + 
while(us--))
- per _asm_ volatile ("nop"); mit verschiedenen nop-Anzahlen und 
while(us--) nop();

Hat alles bisher nicht geklappt! Hat evtl. jemand eine Idee oder sogar 
Code (wenn möglich in C), ohne dass ich noch weitere Bauteile 
"anschweißen" muss.

Vielen lieben Dank!
Gruß
Erik

von Peter D. (peda)


Lesenswert?

Wenn das Timing nicht stimmt, prüfe erstmal, ob es wirklich 8MHz sind.

In der Codesammlung ist ein altes Beispiel, wo ich mir das Delay noch 
selber gebastelt hab.
Ich würde aber dazu raten, die jetzigen Delay-Macros des AVR-GCC zu 
nehmen, die sind ausreichend genau.
Natürlich muß man -Os benutzen.

Was Du nicht benutzen darfst, sind long long Variablen (64 Bit), da 
verpulvert der AVR-GCC Unmengen an CPU-Zeit und Flash, sogar float ist 
schneller.


Peter

von Chris (Gast)


Lesenswert?

Ich hatte bei einem Projekt ähnliche Probleme. Da lag es an den 
aktivierten Interrupts, die das Timing störten. Also vor jedem Zugriff 
auf die Sensoren erst die Interrupts abschalten.

Ansonsten habe ich das einfach mit delays "sequentiell" gelöst und mit 
einem Logic Analyzer kontrolliert. Durch den niedrigen Takt bei meinem 
Projekt beeinflussten schon die Funktionsaurufe zum Schreiben der 
einzelnen Bytes das Timing, d.h. ich musste die delays kürzer machen.

Ansonsten solltest du noch ein paar Infos geben.
Nutzt du 3 Pins des Sensors (VCC, Data, GND) oder nur 2? Wenn du 2 Pins 
benutzt, musst du die Pins VCC und GND verbinden und auf GND legen. 
Deine Software muss entsprechend angepasst sein so dass sie den Sensor 
während der Messung mit Strom versorgt (für ich glaube 750ms). In dieser 
Zeit darf die Leitung nicht Low werden, sonst ließt du nur 85 Grad.

von Klaus W. (mfgkw)


Lesenswert?

Kann es sein, daß Delays in diesem Fall ziemlicher Murks sind?

von Peter D. (peda)


Lesenswert?

Klaus Wachtler schrieb:
> Kann es sein, daß Delays in diesem Fall ziemlicher Murks sind?

Nein.

Peter

von Karl H. (kbuchegg)


Lesenswert?

Klaus Wachtler schrieb:
> Kann es sein, daß Delays in diesem Fall ziemlicher Murks sind?

Eigentlich nicht.
Peters Code hat bei mir auf Anhieb funktioniert.

Allerdings hab ich auch Probleme, wenn Interrupts erlaubt sind -> cli / 
sei verwenden.

von Klaus W. (mfgkw)


Lesenswert?

ok, hat hoffentlich niemand persönlich genommen.

Aber es ist doch so, daß man ein genaues Timing mit _delay...
nicht hinbekommt und gleichzeitig noch etwas anders machen kann
als die Temperatur zu lesen; also kann man ausschließlich
sequentiell arbeiten.
Wenn das reicht, kann man es natürlich so machen.

von Karl H. (kbuchegg)


Lesenswert?

Klaus Wachtler schrieb:

> Aber es ist doch so, daß man ein genaues Timing mit _delay...
> nicht hinbekommt und gleichzeitig noch etwas anders machen kann
> als die Temperatur zu lesen; also kann man ausschließlich
> sequentiell arbeiten.

Grundsätzlich richtig.

Ich bin da pragmatisch vorgegangen:

Den DS1820 auslesen sind nur wenige Bytes hin und her
Pro Byte sind Interrupts gesperrt.
Das Auslesen passiert alle heiligen Zeiten mal (alle 8 Sekunden)
Bei mir ist das schnell genug, dass ich einen anderen wichtigen
Interrupt nicht versäume (INT0, 50Hz generiert aus 230V, für die Uhr)
Der Rest ist nicht zeitkritisch.
Und am wichtigsten: Der Code war schon da :-)

von Peter D. (peda)


Lesenswert?

Klaus Wachtler schrieb:
> Aber es ist doch so, daß man ein genaues Timing mit _delay...
> nicht hinbekommt und gleichzeitig noch etwas anders machen kann
> als die Temperatur zu lesen; also kann man ausschließlich
> sequentiell arbeiten.

So genau muß das ja auch nicht sein.

Der Resetpuls kann 480 ... 960µs lang sein.
Wenn also alle Interrupts zusammen <480µs dauern, braucht man dabei die 
Interrupts nicht zu sperren.

Die Daten werden dann bitweise übertragen, da empfiehlt es sich die 
Interrupts für ein Bit (60µs) zu sperren.
60µs Interruptlatenz sollten in der Regel zu verkraften sein.


Peter

von Klaus W. (mfgkw)


Lesenswert?

So, Ende der Diskussion, ich nehme alles zurück.
Ich hatte in meiner geistigen Umnachtung nicht den DS1820 vor
Augen, sondern den TSic306. Da geht es wesentlich genauer zu.
Hier müsste es auch entspannter klappen.

von Klaus W. (mfgkw)


Lesenswert?

Um auch etwas positives beizutragen:
Hier die relevanten Abschnitte aus einem erfolgreichen Versuch mit einem 
DS18S20:
1
#include <avr/io.h>
2
#include <inttypes.h>
3
#include <util/delay.h>
4
5
typedef union
6
{
7
  uint8_t     byte;
8
  struct
9
  {
10
    unsigned int bit0:1;
11
    unsigned int bit1:1;
12
    unsigned int bit2:1;
13
    unsigned int bit3:1;
14
    unsigned int bit4:1;
15
    unsigned int bit5:1;
16
    unsigned int bit6:1;
17
    unsigned int bit7:1;
18
  } s;
19
} byte8bits_t;
20
21
22
#ifdef DDRA
23
#define ddra  (*(volatile byte8bits_t*)(&DDRA))
24
#define porta (*(volatile byte8bits_t*)(&PORTA))
25
#define pina  (*(volatile byte8bits_t*)(&PINA))
26
#endif /* ifdef DDRA */
27
28
#ifdef DDRB
29
#define ddrb  (*(volatile byte8bits_t*)(&DDRB))
30
#define portb (*(volatile byte8bits_t*)(&PORTB))
31
#define pinb  (*(volatile byte8bits_t*)(&PINB))
32
#endif /* ifdef DDRB */
33
34
#ifdef DDRC
35
#define ddrc  (*(volatile byte8bits_t*)(&DDRC))
36
#define portc (*(volatile byte8bits_t*)(&PORTC))
37
#define pinc  (*(volatile byte8bits_t*)(&PINC))
38
#endif /* ifdef DDRC */
39
40
#ifdef DDRD
41
#define ddrd  (*(volatile byte8bits_t*)(&DDRD))
42
#define portd (*(volatile byte8bits_t*)(&PORTD))
43
#define pind  (*(volatile byte8bits_t*)(&PIND))
44
#endif /* ifdef DDRD */
45
46
47
48
#define DDR_DS18S20_VCC     ddrd.s.bit1
49
#define PORT_DS18S20_VCC    portd.s.bit1
50
#define PIN_DS18S20_VCC     pind.s.bit1
51
52
#define DDR_DS18S20_DQ      ddrd.s.bit2
53
#define PORT_DS18S20_DQ     portd.s.bit2
54
#define PIN_DS18S20_DQ      pind.s.bit2
55
56
57
58
59
uint8_t reset1wire()
60
{
61
  int cnt_wait = 0;
62
  PORT_DS18S20_DQ = 0;
63
  _delay_us( 480 );
64
  PORT_DS18S20_DQ = 1;
65
  // jetzt wartet der slave 15...60 usec und zieht dann die Leitung
66
  // für 60...240 sec auf low (presence pulse).
67
  // Also Pin als Eingang setzen, 60 usec warten und dann warten,
68
  // bis die Leitung wieder hoch geht:
69
  DDR_DS18S20_DQ  = 0;
70
  _delay_us( 60 );
71
  while( 1 )
72
  {
73
    if( PIN_DS18S20_DQ )
74
    {
75
      break;
76
    }
77
    cnt_wait++;
78
    _delay_us( 1 );
79
  }
80
  DDR_DS18S20_DQ  = 1; // auf Ausgang setzen
81
  PORT_DS18S20_DQ = 1; // high
82
  return cnt_wait;
83
}
84
85
86
void putc1wire( uint8_t c )
87
{
88
  for( uint8_t iBit=0; iBit<8; ++iBit )
89
  {
90
    if( c&1 )
91
    {
92
      // 1 schreiben
93
      // Leitung auf low, gleich wieder (maximal in 15 usec, aber
94
      // warum nicht gleich?) wieder auf high und 60 usec warten:
95
      PORT_DS18S20_DQ = 0;
96
      _delay_us( 1 );
97
      PORT_DS18S20_DQ = 1;
98
      _delay_us( 60 );
99
    }
100
    else
101
    {
102
      // 0 schreiben
103
      // Leitung auf low, 60 usec warten, dann wieder auf high:
104
      PORT_DS18S20_DQ = 0;
105
      _delay_us( 60 );
106
      PORT_DS18S20_DQ = 1; // high
107
    }
108
    _delay_us( 1 ); // Sicherheitsabstand zwischen zwei Bits
109
    c >>= 1;
110
  }
111
}
112
113
114
// liest ein Byte
115
uint8_t getc1wire()
116
{
117
  uint8_t   ret = 0;
118
  for( uint8_t iBit=0; iBit<8; ++iBit )
119
  {
120
    ret >>= 1;
121
122
    // Leitung auf low, gleich wieder (nach min. 1 usec) wieder auf
123
    // high und innerhalb der nächsten 15 usec lesen:
124
    PORT_DS18S20_DQ = 0;
125
    _delay_us( 1 );
126
    PORT_DS18S20_DQ = 1;
127
    DDR_DS18S20_DQ  = 0;
128
    _delay_us( 10 );
129
    if( PIN_DS18S20_DQ )
130
    {
131
      // 1 gelesen
132
      ret |= 0b10000000;
133
    }
134
    // den Rest auf 60 usec warten:
135
    _delay_us( 60 );
136
    DDR_DS18S20_DQ  = 1; // Ausgang
137
138
    _delay_us( 1 ); // Sicherheitsabstand zwischen zwei Bits
139
  }
140
  return ret;
141
}
142
143
144
// liest ein Bit
145
uint8_t getb1wire()
146
{
147
  uint8_t   ret = 0;
148
  // Leitung auf low, gleich wieder (nach min. 1 usec) wieder auf
149
  // high und innerhalb der nächsten 15 usec lesen:
150
  PORT_DS18S20_DQ = 0;
151
  _delay_us( 1 );
152
  PORT_DS18S20_DQ = 1;
153
  DDR_DS18S20_DQ = 0; // Eingang
154
  _delay_us( 10 );
155
  if( PIN_DS18S20_DQ )
156
  {
157
    // 1 gelesen
158
    ret |= 0b1;
159
  }
160
  // den Rest auf 60 usec warten:
161
  _delay_us( 60 );
162
  DDR_DS18S20_DQ = 1; // Ausgang
163
164
  _delay_us( 1 ); // Sicherheitsabstand zwischen zwei Bits
165
  return ret;
166
}
167
168
169
170
int main( int nargs, char **args )
171
{
172
173
  // Pin DQ erstmal als Ausgang und high setzen:
174
  DDR_DS18S20_DQ  = 1;
175
  PORT_DS18S20_DQ = 1;
176
177
  DDR_DS18S20_VCC  = 1;  // VCC für DS18S20 ausschalten
178
  PORT_DS18S20_VCC = 0;
179
180
  reset1wire();
181
182
183
  while( 1 )
184
  {
185
    uint8_t   t_ist; // IST-Temperatur [grad C]
186
187
    reset1wire();
188
    // Initialization:
189
    PORT_DS18S20_VCC  = 1;   // VCC einschalten
190
    // 1. reset pulse (mind. 480 usec low)
191
    // presence pulse ist rum
192
193
    // ROM Command
194
    // READ ROM (0x33) kann nur verwendet werden, wenn es nur einen
195
    // slave gibt; danach könnte man das ROM lesen.
196
    // SKIP ROM (0xCC) kann nur verwendet werden, wenn es nur einen
197
    // slave gibt.
198
    //putc1wire( 0x33 );
199
    putc1wire( 0xCC );
200
201
    // Function Command
202
    // convert command
203
    putc1wire( 0x44 );
204
205
    // warten, bis ein 1-Bit gelesen wird:
206
    while( !getb1wire() )
207
    {
208
      _delay_us( 1 );
209
      // TODO: notfalls abbrechen!
210
    }
211
212
    // jetzt wieder Reset und Daten lesen:
213
    reset1wire();
214
    putc1wire( 0xCC ); // SKIP ROM
215
    putc1wire( 0xBE ); // READ SCRATCHPAD
216
217
    uint8_t    data1wire[9];
218
    for( int iData=0; iData<9; ++iData )
219
    {
220
      data1wire[iData] = getc1wire();
221
    }
222
    PORT_DS18S20_VCC  = 0;   // VCC ausschalten
223
224
    // Temperatur in Grad C
225
    t_ist = ( ( (int)data1wire[1] << 8 ) | data1wire[0] )/2;
226
    _delay_ms( 1000 );
227
  }
228
229
  return 0;
230
}

Das hat als C++ vor etwa einem halben Jahr auf einem Mega8 funktioniert
und ist hier auf das DS18S20-Relevante eingedampft.
Es ist so als C kompilierbar, getestet habe ich es aber nicht
mehr weiter.

von Erik (Gast)


Lesenswert?

So, erstmal vielen Dank soweit!
Werde mal schauen, ob der Code läuft.
Der Witz ist, ich habe keine Interrupts mehr an und der DS1820 ist mit 
VCC und GND verbunden (also kein Parasiten-Power :-) )
Habe es auch schon mit "_delay_us( 480 );"... versucht, hat aber 
irgendwie nicht geklappt.
Evtl. steckt der Teufel ja irgendwo im Detail.
Werde es testen und ggf. nochmal SOS rufen!

Gruß
Erik

von Peter D. (peda)


Lesenswert?

Erik schrieb:
> Habe es auch schon mit "_delay_us( 480 );"... versucht, hat aber
> irgendwie nicht geklappt.

Dann stimmt Deine F_CPU Definition nicht mit der tatsächlichen 
AVR-Frequenz überein.

Der AVR-GCC kann ja nicht riechen, welche Fuses und Prescaler Du gesetzt 
hast und welcher Quarz angeschlossen ist. Du mußt es ihm schon sagen.

Du solltest auch die Compiler-Warnungen lesen.


Peter

von Erik (Gast)


Lesenswert?

@Peter Dannegger
Hey, hey, selbstverständlich stimmt das ;-)
F_CPU=8000000, CLK: 8Mhz, intern, CKDIV ist nicht gesetzt (also keine 
Teilung) => alles richtig => keine Warnung

Hatte ich nur vergessen zu schreiben, sorry.

Gruß
Erik

von Jojo (Gast)


Lesenswert?

Also ich hab auch mal den DS1820 benutzt und der ist eigentlich wirklich 
recht tolerant bei den Zeiten. Bei mir lag es damals an was ganz 
anderem:
ich hatte auch nur einen Sensor dran und dachte darum, ich müsse mich 
beim Auslesen auch um keine IDs kümmern, und so. Aber: dann muß man 
das dem Sensor auch sagen! Ich hab den Befehl nicht mehr im Kopf, aber 
man kann was senden, was dann den Abgleich mit der Sensor-ID 
überspringt. Als das drin war hat es super geklappt, lag also an meinem 
Protokoll...
Sicher, daß das bei dir richtig ist?

Gruß

von Erik (Gast)


Lesenswert?

Hallo nochmals...
Danke für die vielen Rückmeldungen!
> Bei mir lag es damals an was ganz anderem
Unglaublich aber wahr: Alle drei (!) Sensoren, die ich hatte, waren 
defekt. Sie haben zwar das Kommando zur Temp.-Messung angenommen, dann 
aber immer den Default von 85°C zurück gemeldet.
Fazit: Neuer Sensor angeschlossen und alles rennt!
Der Lerneffekt: Ich habe jetzt ca. 8 Arten griffbereit um Timings zu 
erzeugen. ( Und es geht mit allen :-) )

...Murphy lässt grüßen...

Danke an alle & Gruß
Erik

von Peter D. (peda)


Lesenswert?

Erik schrieb:
> Unglaublich aber wahr: Alle drei (!) Sensoren, die ich hatte, waren
> defekt. Sie haben zwar das Kommando zur Temp.-Messung angenommen, dann
> aber immer den Default von 85°C zurück gemeldet.

Die müssen aber nicht kaputt sein.
Warscheinlich sinds nur DS1820-PAR.

Da ist VCC nicht nach außen geführt, sondern intern mit GND verbunden.
D.h. ohne parasite Power liefern die immer 85°C

Parasite Power geht aber recht einfach:
Direkt nach dem Start Conversion Kommando den Pin auf high Output setzen 
für ~1s.

Guck mal, ob hinter der 20 nicht noch ein P steht.


Peter

von Erik (Gast)


Lesenswert?

@Peter
> ...sondern intern mit GND verbunden.
Da ich aber VCC mit 5V versorgt habe, hätte es doch schön rauchen 
müssen. Der Stromfluss war aber nicht hoch.
> Guck mal, ob hinter der 20 nicht noch ein P steht.
Nein, kein "P". Also wohl doch kaputt.
Denke jetzt aber darüber nach die neuen DS1820 mit parasite Power zu 
betreiben. So kann ich eine weitere Leitung einsparen.

Danke & Gruß
Erik

von (prx) A. K. (prx)


Lesenswert?

Was Delays angeht: Bei mir läuft Peters Code u.A. leicht variiert unter 
einem RTOS, bei dem nur die kurzen Delays bis zum unteren 2-stelligen 
µs-Bereich per Warteschleife mit abgeschalteten Interrupts abgesichert 
sind und der Rest über das RTOS-Scheduling mit dessen 10KHz Timer-Tick 
abgewickelt wird.

Das einzige Timing-Problem ergab sich durch einen kleinen Fehler in der 
Originalversion, der bei höherer Taktfrequenz zu einer zu kurzen 
Erholzeit zwischen den Bits führte. Die nicht ausreichte, um eine lange 
Leitung passiv wieder sauber auf 1 zu ziehen (im zuständigen Thread 
dokumentiert). An dieser Stelle fehlte ein Delay von ein paar µs.

von Peter D. (peda)


Lesenswert?

Erik schrieb:
> Da ich aber VCC mit 5V versorgt habe, hätte es doch schön rauchen
> müssen.

Nö, beim -PAR ist der Pin ja unbeschaltet.
Du kannst also nichtmal die Substratdiode messen.

Peter

von Erik (Gast)


Lesenswert?

@Peter
Jo, hatte das "intern" überlesen.

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.