Forum: Mikrocontroller und Digitale Elektronik Timing Probleme 1-Wire


von Anon A. (anon1234)



Lesenswert?

Halli hallo...
ich habe jetzt wieder mit der Implementierung meiner 1-Wire 
Temperatursensoren DS18B20 weiter gemacht.
Das Protokoll habe ich eigentlich schon mehrfach durchgearbeitet und 
auch unterschiedliche Programmschnipsel und dachte eigentlich ich hätte 
es einigermaßen verstanden.
Nun zeigt der Test mit dem Logikanalysator leider, dass irgendwas schief 
läuft.
So wie ich es verstanden habe, ist die Theorie doch wie folgt:

WRITE 0:
 > pull bus low
 > wait >= 60µs
 > release bus
 > wait 10µs

WRITE 1:
 > pull bus low
 > wait <= 15µs
 > release bus
 > wait 64µs

Ich habe hierfür in 8051er assembler folgende funktionen geschrieben:
1
; write lsb of a to bus
2
; write 0:
3
;   - pull bus LOW
4
;   - wait >=60µs (60µs)
5
;   - release bus
6
;   - wait 10µs
7
; write 1:
8
;   - pull bus LOW
9
;   - wait <= 15µs (6µs)
10
;   - release bus
11
;   - wait 64µs
12
write:
13
  mov r0,a   ; save a
14
  jnz high_1
15
  mov r5,#3Ch   ; write 0:
16
  mov r6,#0h    ;   wait-time 60µs
17
  jmp wait_1 
18
  high_1:
19
    mov r5,#6h   ; write 1:
20
    mov r6,#0h   ;   wait-time 6µs
21
  wait_1:
22
    clr p2.4      ; pull bus LOW
23
    call initTimer
24
    jnb tf1,$     ; wait till timer-overflow
25
    setb p2.4     ; release bus
26
    clr tr1      ; stop timer
27
  mov a,r0    ; get a back
28
  jnz high_2
29
  mov r5,#0Ah    ; write 0:
30
  mov r6,#0h     ;   wait-time 10µs  
31
  jmp wait_2
32
  high_2:           
33
    mov r5,#40h   ; write 1:
34
    mov r6,#0h    ;   wait-time 64µs
35
  wait_2:
36
    call delay
37
  ret  
38
39
;------------------------------------------------------------------------
40
;Timer
41
;------------------------------------------------------------------------
42
43
;sets TimerValue to FFFFFFFF - time in µSeconds
44
;inits and starts Timer
45
initTimer:
46
  ; sets TimerValue to FFFFFFFF - time in µSeconds
47
   mov a,#0FFh
48
  clr cy
49
  subb a,r5
50
  mov tl1,a
51
  mov a,#0FFh
52
  subb a,r6
53
  mov th1,a
54
  
55
  ; inits the timer
56
  mov tmod,#00010000b 
57
  ; Timer 1 zählt jeden 12. Takt = 1MHz
58
  ; 0 = keine externe Zählersteuerung durch Torschaltung
59
  ; 0 = interner Zeitgeber
60
  ; 01 = 16 Bit-Zähler
61
  ; Timer 0
62
  ; 0000 = aus
63
  clr tf1  ; overflow-flag zurücksetzen
64
  setb tr1 ; Starten des Timers
65
  ret
66
 
67
;sets TimerValue to FFFFFFFF - time in µSeconds
68
;inits and starts Timer
69
;and waits till Timeroverflow
70
delay:
71
  call initTimer
72
  jnb TF1,$   ; notify TimerOverflow  
73
  clr tr1     ; stop timer
74
  ret

Das Timing wird hierbei über den Timer gesteuert. Ein Timer-Schritt 
entspricht 1µs. Es muss daher nur die Anzahl an µs in die Register r5 
und r6 geschrieben werden und der Timer angeschubst werden. Das 
funktioniert auch eigentlich soweit.
Wie man allerdings in dem angehängten bildern sieht, sind die zeiten, 
die der bus für eine 0 bzw. 1 auf low gezogen werden muss nicht richtig.

bei einer 0 sind es nur ca. 20µs die das signal auf low ist und bei 
einer 1 ganze 74µs..

jetzt ist meine frage :
1. ist einfach mein grundverständnis vom timing und somit auch mein 
programm falsch ?
2. oder liegt das problem nicht an meinem verständnis, sondern daran, 
dass vlt doch ein fehler in der zeitsteuerung mit den timern vorliegt?
3. warum erkennt der protocol-analyzer die 0-en, obwohl die zeit auch 
hier ja garnicht passt.

Im Anhang habe ich den kompletten Code angehängt, der einfach nur die 
Adresse des Sensors auslesen und ausgeben soll.
Außerdem hab ich mal das Datenblatt und die Screenshots vom 
Logikanalysator mit hochgeladen.

Wäre echt cool, wenn mir jemand helfen könnte :)
Gruß Anon1234

: Bearbeitet durch User
von Easylife (Gast)


Lesenswert?

Ohne viel davon zu verstehen, aber folgende Stelle verstehe ich nicht:
1
  ; sets TimerValue to FFFFFFFF - time in µSeconds
2
   mov a,#0FFh
3
  clr cy
4
  subb a,r5
5
  mov tl1,a
6
  mov a,#0FFh
7
  subb a,r6
8
  mov th1,a

offenbar ist dein timer 32 bit breit, und du setzt 2x16-bit values über 
t11 und th1...?!

#0FFh ist aber nicht #FFFFh, was ich an dieser Stelle vermuten würde...
Wie breit sind denn die Register a, tl1, th1 in deinem System?

So wie es mir aussieht, setzt du den timer auf einen falschen Startwert 
(sowas wie 0x00ff00ff - r6<<16 - r5)

von Easylife (Gast)


Lesenswert?

Ausserdem kannst du dir das ganze Gedöhns mit #0 in r6 schreiben, um 
dann später  subb a,r6 zu machen komplett sparen, solange dein delay < 
255 oder evtl. auch 65535us (falls die Register 16 bit breit sind) 
bleibt...

von Anon A. (anon1234)


Lesenswert?

Easylife schrieb:
> Ohne viel davon zu verstehen, aber folgende Stelle verstehe ich
> nicht:  ; sets TimerValue to FFFFFFFF - time in µSeconds
>    mov a,#0FFh
>   clr cy
>   subb a,r5
>   mov tl1,a
>   mov a,#0FFh
>   subb a,r6
>   mov th1,a
>
> offenbar ist dein timer 32 bit breit, und du setzt 2x16-bit values über
> t11 und th1...?!
>
> #0FFh ist aber nicht #FFFFh, was ich an dieser Stelle vermuten würde...
> Wie breit sind denn die Register a, tl1, th1 in deinem System?
>
> So wie es mir aussieht, setzt du den timer auf einen falschen Startwert
> (sowas wie 0x00ff00ff - r6<<16 - r5)

danke für deine Schnelle antwort.
der kommentar ist falsch. FFFFFFFF müsste eigentlich FFFF heißen. Die 
beiden Register sind nämlich jeweils 8-Bit. Zusammen ergibt das dann 
einen 8-Bit Timer.

die Null vor dem #0FFh steht da nur, damit der kompiler den Wert FF als 
Wert erkennt. Muss man leider so schreiben, weil F ja ein buchstabe 
ist..

von Peter D. (peda)


Lesenswert?

Der ganze Schrunz in Deinem initTimer: kostet natürlich auch massig 
Zeit.

Und ist außerdem falsch, Du mußt das 2-er Komplement bilden, also 
-delay.

Du kannst aber besser das Rechnen zur Laufzeit ganz sparen, der 
Assembler kann nämlich 16Bit Konstantenrechnung selber ausführen:
1
  delay_60us equ 60
2
3
  mov tl0, #low( -delay_60us )
4
  mov th0, #high( -delay_60us )

von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

Anon Anon schrieb:
> 1. ist einfach mein grundverständnis vom timing und somit auch mein
> programm falsch ?

 Du machst das viel zu kompliziert.
 Bei 1-Wire ist:
 Schreiben:
    Log.1 = 10us Low, 60us Release.
    Log.0 = 60us Low, 10us Release.
 Lesen:
    Log.1 = 10us Low, 60us Release.

 Man hat also immer zuerst 10us Low, danach wird der Bus entweder
 weiter auf Low gehalten oder freigegeben.
 Die Antwort von Slave liest man nach weiteren 20us, auch wenn keine
 vorgesehen ist. Nach weiteren 30us wird der Bus für 10us freigegeben.

 Somit hast du nur 1 Stelle wo sich etwas ändert und das ist ganz
 einfach (auch in Software mit Loop und NOPs) zu realisieren.
 Also:
  Bus Low
    Delay 10us.
        Bus entsprechend bitzustand setzen.
    Delay 20us.
  Bus lesen.
    Delay 30us.
    Bus freigeben.
    Delay 10us.

 Das Ganze 8 mal im Loop.

 EDIT:
  Beim lesen einfach 0xFF senden.

: Bearbeitet durch User
von Anon A. (anon1234)


Lesenswert?

Danke für die ganzen schnellen Antworten :) :)

Peter Dannegger schrieb:
> Du kannst aber besser das Rechnen zur Laufzeit ganz sparen, der
> Assembler kann nämlich 16Bit Konstantenrechnung selber ausführen:
> delay_60us equ 60
>
>   mov tl0, #low( -delay_60us )
>   mov th0, #high( -delay_60us )
das ist natürlich ziemlich cool, das kannte ich noch garnicht. Danke für 
den Tipp.

Peter Dannegger schrieb:
> Und ist außerdem falsch, Du mußt das 2-er Komplement bilden, also
> -delay.

Das ist mir gerade nicht klar, warum ich das machen soll. Verkompliziert 
das nicht eher alles.
Momentan subtrahiere ich ja mit subb die benötigte zeit von FFFF. Sollte 
doch eigentlich so funktionieren?

Marc Vesely schrieb:
> Man hat also immer zuerst 10us Low, danach wird der Bus entweder
>  weiter auf Low gehalten oder freigegeben.
>  Die Antwort von Slave liest man nach weiteren 20us, auch wenn keine
>  vorgesehen ist. Nach weiteren 30us wird der Bus für 10us freigegeben.

Danke für das Beispiel :)
Insgesamt würde es dann so aussehen oder?
1
; write lsb of a to bus
2
;   - pull bus LOW
3
;   - wait 10µs
4
;   - if HIGH
5
;       > release bus 
6
;    > wait 60µs
7
;   - if LOW
8
;       > wait 50µs
9
;    > release bus
10
;       > wait 10µs
11
;  - wait 20µs
12
;   - read bus
13
;   - save bus
14
;  - wait 30µs
15
;   - release bus
16
;  - wait 10µs

Die Idee mit den Nops finde ich allerdings nicht so elegant. wie mit dem 
Timer.. Ist halt die Frage, was hier zielführender ist.

von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

Anon Anon schrieb:
> Insgesamt würde es dann so aussehen oder?

 Eher so:
1
w1_Read:
2
;***  a wird auf 0xFF gesetzt (zum lesen)
3
   mov a,#0FFh
4
;*** Und durchfallen auf w1_Write...
5
6
w1_Write:
7
;*** Wert zum schreiben in a
8
    mov  r5, 8             ;* 8 bits
9
10
w1_loop:
11
;   - pull bus LOW
12
;   - wait 10µs
13
;   - if HIGH              ;* Es wird bit 0 in a geprüft
14
;       > release bus
15
16
; ...
17
;***  Unnötiges raus
18
; ...
19
20
;  - wait 20µs
21
;   - read bus             ;* Bus wird IMMER gelesen !
22
23
;* Der gelesene bit wird nach rechts in a reingeschoben, bit0 wird
24
;* rausgeschoben, bit1 wird zum bit0 etc.
25
;   - save bus             ;* Shift bit right in a
26
27
;  - wait 30µs
28
;   - release bus
29
;  - wait 10µs
30
31
;*** Decrement r5 und jump nach w1_loop

 Diese routine gibt den gelesenen Byte in a zurück.

Anon Anon schrieb:
> Die Idee mit den Nops finde ich allerdings nicht so elegant. wie mit dem
> Timer.. Ist halt die Frage, was hier zielführender ist.

 Unwichtig.
 Mach es halt so, wie es dir am Besten erscheint.

: Bearbeitet durch User
von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

Anon Anon schrieb:
> Das ist mir gerade nicht klar, warum ich das machen soll. Verkompliziert
> das nicht eher alles.

 Nein, wieso ?
 Der Compiler rechnet das für dich, wenn das verkomplizieren ist...

Anon Anon schrieb:
> Momentan subtrahiere ich ja mit subb die benötigte zeit von FFFF. Sollte
> doch eigentlich so funktionieren?

 Auch wieder nein.
 Der Compiler hätte es schon richtig gemacht, du nicht.
 Überlauf tritt erst beim FFFF -> 0 auf, nicht schon beim 0xFFFF.

 -60 ist eben (0 - 60) ;-D
 Das hat der PeDa gemeint.

von Route_66 H. (route_66)


Lesenswert?

Anon Anon schrieb:
> Die Idee mit den Nops finde ich allerdings nicht so elegant. wie mit dem
> Timer.. Ist halt die Frage, was hier zielführender ist.

Bei solch kurzen Wartepausen sind NOP-Delays nicht so falsch, vor allem, 
wenn die beiden Timer bereits verwendet werden, z.B. für UART und Uhr.

von Anon A. (anon1234)


Lesenswert?

Marc Vesely schrieb:
> Eher so:

Aah okay.. also das mit der Schleife hätte ich schon noch gemacht :D
aber das mit dem überflüssigen waits für LOW hatte ich nicht so 
geblickt. Jetzt wo ichs lese ist es aber klar.

von Peter D. (peda)


Lesenswert?

Ich hab ja früher mal 8051 Assembler gemacht:
1
xtal  equ  11059                   ; kHz
2
w1_dio  equ  p2.3
3
4
w1w10  equ   10 * ( xtal / 100 ) / 120
5
w1w60  equ   60 * ( xtal / 100 ) / 120
6
w1w480  equ  480 * ( xtal / 100 ) / 120
7
w1w66  equ   66 * ( xtal / 100 ) / 120
8
9
;******************************  Generate a 1-Wire reset *****************/
10
;Input: none
11
;Output: CY = 1, if no 1-wire device found
12
;Used:  R2, CY
13
;
14
w1_reset:
15
  mov  r2, #w1w480 / 4      ;480 < t < 960
16
?w1r1:  clr  w1_dio
17
  nop
18
  djnz  r2, ?w1r1
19
  clr  ea
20
  setb  w1_dio
21
  mov  r2, #w1w66 / 2      ;60 < t < 75
22
  djnz  r2, $
23
  mov  c, w1_dio
24
  setb  ea
25
  mov  r2, #(w1w480 - w1w66) / 4  ;480 < t
26
?w1r2:  nop
27
  nop
28
  djnz  r2, ?w1r2
29
  orl  c, /w1_dio
30
  ret
31
32
;******************************  Read / Write Bit on 1-wire **************/
33
;Input  CY = bit
34
;Output  CY = bit
35
;Used  R2, CY
36
;
37
w1_bit_rd:
38
  setb  c
39
w1_bit_wr:
40
w1_bit_io:
41
  clr  ea
42
  clr  w1_dio
43
  mov  r2, #w1w10 / 2                  ; djnz = 2 cycle
44
  mov  w1_dio, c
45
  djnz  r2, $
46
  mov  c, w1_dio      ; sample within 15us
47
  mov  r2, #(w1w60 - w1w10) / 2
48
  djnz  r2, $
49
  setb  w1_dio
50
  setb  ea
51
  ret
52
53
;******************************  Read / Write byte ***********************/
54
;Input  R7 = byte
55
;Output R7 = byte
56
;Used  R2, R7, ACC, CY
57
;
58
w1_byte_rd:
59
  mov  r7, #0FFh
60
w1_byte_wr:
61
  mov  a, r7
62
  mov  r7, #8
63
?w1wr1:
64
  rrc  a      ; lsb first
65
  call  w1_bit_io
66
  djnz  r7, ?w1wr1
67
  rrc  a      ; last bit in
68
  mov  r7, a
69
  ret

von Anon A. (anon1234)


Lesenswert?

Hallo nochmal...
danke für eure guten Antworten.
Es hat bei mir jetzt wieder mal ein bisschen gedauert, bis ich hier 
weitergemacht hab, aber nach euren guten Tipps habe ich jetzt ein paar 
Fortschritte.
Zuerst hab ich mein Glück noch einmal mit dem Timer versucht, musste 
dann aber feststellen, (wie ihr ja schon geschrieben hattet), dass die 
ganzen Befehle zum Einstellen und Starten des Timers schon viel zu viel 
Zeit verbraucht haben und somit beim Timing (logischer weise) nichts 
mehr gepasst hat.
Deswegen bin ich jetzt doch auf die Variante mit den Nops umgestiegen. 
Und hab mich da ein bisschen an das gehalten, was peda in seinem code 
mit den oben berechneten Konstanten gemacht hat.

Bei mir sieht der code jetzt wie folgt aus :
1
;------------------------------------------------------------------------
2
;READ / WRITE
3
;------------------------------------------------------------------------
4
5
; reads one Byte and saves it in r7
6
readByte:
7
  mov r7,#0FFh
8
  ; goes down to writeByte
9
10
; write the 8 bits of r7 to the bus
11
; uses r1 for a loop
12
writeByte:
13
  mov r2,#8
14
  write8BitsLoop_1:
15
    call write_read      ; write lsb of r7 a
16
    djnz r2,write8BitsLoop_1
17
    ret
18
19
20
; writes lsb to bus and / or reads bus into msb of r7
21
22
; pull bus low
23
; wait 10µs
24
; if lsb = HIGH
25
;   > release bus
26
; else 
27
;   > bus stays low
28
; wait 20µs
29
; read and save bus
30
; wait 30µs
31
; release bus 
32
; wait 10µs
33
write_read:
34
  clr p2.4  ; pull bus LOW
35
  mov r1,#wait10                ;
36
  wait10loop_1:            ; wait 10µs
37
    nop                ;
38
    djnz r1,wait10loop_1  ;
39
  mov a,r7
40
  jnb acc.0,read
41
  setb p2.4 ; release bus if r7.0 is HIGH
42
  read:            
43
    mov r1,#wait20           ; 
44
    wait20loop_1:            ; wait 20µs
45
      nop        ;
46
      djnz r1,wait20loop_1      ; 
47
    mov a,r7
48
    rr a  ; rotate a
49
    jb p2.4,high_1
50
    anl a,#01111111b ; set msb to 0
51
    jmp afterRead_1
52
    high_1:
53
      anl a,#11111111b ; set msb to 1
54
  afterRead_1:    
55
    mov r7,a
56
    mov r1,#wait30        ;
57
    wait30loop_1:         ; wait 30µs
58
      nop                  ;
59
      djnz r1,wait30loop_1     ;
60
    setb p2.4 ; release bus
61
    mov r1,#wait10         ;
62
    wait10loop_2:          ; wait 10µs
63
      nop        ;
64
      djnz r1,wait10loop_2          ;
65
    ret

Hiermit hat es bei mir geklappt einen READROM Befehl zu schicken und die 
Adresse des sensors zu lesen.
Die Adresse die ich hierbei mit dem Logikanalysator gelesen habe war die 
folgende
1
00101000 28h Family Code
2
01000101 LSB Serial Code
3
01111111 ...
4
10101111 ...
5
00000101 ...
6
00000000 ...
7
00000000 MSB Serial Code
8
10000100 CRC

Da der sogenannte Family Code mit 28h richtig ist, bin ich jetzt 
ersteinmal davon ausgegangen, dass die komplette Adresse richtig ist.
Im nächsten Schritt wollte ich jetzt Befehle wie MatchRom und dann 
ConvertT und ReadScratchpad an meinen Sensor (DS18B20) schicken.

Viele Grüße
Anon1234

von Anon A. (anon1234)


Lesenswert?

Ich habe jetzt gerade mal das CRC nachberechnet
(Dafür gibt es hier ein ganz nettes Tool : 
http://www.datastat.com/sysadminjournal/maximcrc.cgi )
Und demzufolge ist meine Adresse richtig :)

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.