Aus RN-Wissen.de
Wechseln zu: Navigation, Suche
Laderegler Test Tueftler Seite

(PULSEIN)
K (SERVO)
 
(34 dazwischenliegende Versionen von 5 Benutzern werden nicht angezeigt)
Zeile 1: Zeile 1:
==Bascom Inside-Code==
 
 
 
Für den allgemein Interessierten und für Power-User, die ihr Bascom-Programm mit etwas Assembler-Code aufpeppen möchten, stelle ich recht zwanglos einige Assembler-Codeschnipsel zusammen mit den zugehörigen Bascom-Statements zur Verfügung.  
 
Für den allgemein Interessierten und für Power-User, die ihr Bascom-Programm mit etwas Assembler-Code aufpeppen möchten, stelle ich recht zwanglos einige Assembler-Codeschnipsel zusammen mit den zugehörigen Bascom-Statements zur Verfügung.  
  
Die meisten Beispiele könnte man mit Inline-Assemler direkt ersetzen, wenn man mal probieren wollte, man muß natürlich auf Daten- und Labeladressen aufpassen
+
Gleich ein Hinweis: wer daran schrauben möchte, sollte auch bei den [[Bascom Libraries]] als Ergänzung zum Inline Assembler vorbeischauen,
 +
 
 +
Die zugrundeliegende Bascom-Version '''1.11.8.1'''
 +
 
 +
Das Disassembling wurde erstellt mit dem PicNickHexHacker 1.0.x.y.z (Der AVR-Studio Disassembler ist mir zu unleserlich, vielleicht eine Alterserscheinung).
 +
 +
Die meisten Beispiele könnte man mit Inline-Assembler direkt ersetzen, wenn man mal probieren wollte, man muß natürlich auf Daten- und Labeladressen aufpassen
  
 
Die meisten Bascom-Funktionen ergeben im Code dann folgende Teile:  
 
Die meisten Bascom-Funktionen ergeben im Code dann folgende Teile:  
 
*Der Aufruf
 
*Der Aufruf
 
**die Vorbereitung, also das Laden von Registern mit den konkreten Argumenten,
 
**die Vorbereitung, also das Laden von Registern mit den konkreten Argumenten,
**einen Call auf den eigentliche Funktions-code
+
**einen Call auf den eigentliche Funktionscode
 
**das Abliefern des Ergebnisses
 
**das Abliefern des Ergebnisses
 
*Die Funktion selbst
 
*Die Funktion selbst
  
 
+
==ADC==
 
===Config ADC===
 
===Config ADC===
 
  CONFIG Adc = Single , Prescaler = Auto
 
  CONFIG Adc = Single , Prescaler = Auto
Zeile 28: Zeile 32:
 
===Getadc()===
 
===Getadc()===
 
*Aufruf
 
*Aufruf
  DIM X AS WORD                  laut "''prog''.RPT"  an der Adresse 0x0063
+
  DIM X AS WORD                  ' laut "''prog''.RPT"  an der Adresse 0x0063
 
  X = GETADC(0)
 
  X = GETADC(0)
 
ergibt folgenden Code:
 
ergibt folgenden Code:
 
<pre>
 
<pre>
LDI r24,0x00      ADC-Kanal-Nummer nach Register 24
+
LDI r24,0x00      ' ADC-Kanal-Nummer nach Register 24
OUT ADMUX,r24      in den ADC-Multiplexer
+
OUT ADMUX,r24      ' in den ADC-Multiplexer
  
CALL L_0x00F6      Aufruf der getadc-funktion
+
CALL L_0x00F6      ' Aufruf der getadc-funktion
  
LDI XL,0x63        laden der Ergebnisadresse  (DIM X AS WORD)
+
LDI XL,0x63        ' laden der Ergebnisadresse  (DIM X AS WORD)
 
LDI XH,0x00
 
LDI XH,0x00
ST X+,r24        Speichern ergebnis  (R24:r25) in "X"
+
ST X+,r24        ' Speichern ergebnis  (R24:r25) in "X"
 
ST X,r25
 
ST X,r25
 
</pre>
 
</pre>
Zeile 46: Zeile 50:
 
<pre>
 
<pre>
 
L_0x00F6:
 
L_0x00F6:
SBI ADCSR,ADSC  Starten der 1. Konversion
+
SBI ADCSR,ADSC  ' Starten der 1. Konversion
 
L_0x00F8:
 
L_0x00F8:
SBIC ADCSR,ADSC  Fertig ?  
+
SBIC ADCSR,ADSC  ' Fertig ?  
RJMP L_0x00F8    nein, Loop1
+
RJMP L_0x00F8    ' nein, Loop1
SBI ADCSR,ADSC  Starten der 2. Konversion
+
SBI ADCSR,ADSC  ' Starten der 2. Konversion
 
L_0x00FE:
 
L_0x00FE:
SBIC ADCSR,ADSC  Fertig ?  
+
SBIC ADCSR,ADSC  ' Fertig ?  
RJMP L_0x00FE    nein, Loop2
+
RJMP L_0x00FE    ' nein, Loop2
IN r24,ADCL    Ergebnis auslesen r24:r25
+
IN r24,ADCL    ' Ergebnis auslesen r24:r25
 
IN r25,ADCH
 
IN r25,ADCH
RET                  fertig
+
RET                  ' fertig
 
</pre>
 
</pre>
  
===PULSEIN===
+
==BITWAIT==
 +
===Bitvariable===
 +
*Aufruf
 +
Dim A As Bit
 +
BITWAIT A , Set                                            'wait until bit a is set
 +
*Code
 +
<pre>
 +
L_0x007A:
 +
LDS r24,0x0060
 +
SBRS r24,7
 +
RJMP L_0x007A
 +
</pre>
 +
 
 +
===IO-Register===
 +
*Aufruf
 +
BITWAIT Pinb.7 , Reset                                    'wait until bit 7 of Port B is 0.
 +
*Code
 +
<pre>
 +
L_0x008C:
 +
SBIC PINB,PB7
 +
RJMP L_0x008C
 +
</pre>
 +
 
 +
==BITVARIABLE==
 +
Vielleicht zu Erläuterung: Die erste angegebene Bitvariable ist nicht BITCONTAINER.0, sondern BITCONTAINER.7, dann weiter zu BITCONTAINER.6 usw.
 +
 
 +
'''Beispiele'''
 +
 
 +
Eine Bitvariable setzen (oder Löschen), ist harmlos, das geht nicht anders.
 +
<pre>
 +
'----------------------------------
 +
  Bit0 = 1
 +
'----------------------------------------
 +
LDS r24,0x0060
 +
ORI r24,0x80
 +
STS 0x0060,r24
 +
</pre>
 +
Eine Bitvariable in eine andere übertragen zeigt sich schon lebhafter
 +
<pre>
 +
  Bit1 = Bit0
 +
</pre>
 +
 
 +
<pre>
 +
LDI XL,0x60    ' low (Bitcontainer)
 +
LDI XH,0x00    ' high (Bitcontainer)
 +
LD r24,X      ' into R24
 +
BST r24,7     ' BIT0 --> T-Bit
 +
LDI XL,0x60    ' low (Bitcontainer)
 +
LDI XH,0x00    ' high (Bitcontainer)
 +
LD r24,X      ' into R24
 +
BLD r24,6     ' T-BIT --> BIT1
 +
ST X,r24      ' store
 +
</pre>
 +
Da wär für einen Optimizer schon was zu machen, aber das hab' ich nicht probiert.
 +
 
 +
Ein klassische BIT-Variablen Abfrage
 +
<pre>
 +
  If Bit0 = 1 Then
 +
    Bit1 = 1
 +
  Else
 +
      Bit1 = 0
 +
  End If
 +
'----------------------------------------
 +
</pre>
 +
Das zerfällt in zwei Stufen:
 +
Das gefragte Bit wird ins T-Bit transferiert, abhängig davon ist am Ende dann R16 auf 0 oder 1
 +
<pre>
 +
CLR r16
 +
LDI XL,0x60
 +
LDI XH,0x00
 +
LD r24,X
 +
BST r24,7
 +
BRTC L_0x00B0
 +
LDI r16,0x01
 +
L_0x00B0:
 +
</pre>
 +
 
 +
Der Wert "1" wird ins R20 geladen, damit wird nun R16 verglichen.
 +
Es geht dann zu "IF-Then" oder "IF-Else"
 +
<pre>
 +
LDI r20,0x01   
 +
CP r16,r20
 +
BREQ L_0x00BA      ' R16 = 1 --> THEN
 +
JMP L_0x00C8      ' R16 = 0 --> ELSE
 +
 
 +
' ------ THEN
 +
L_0x00BA:
 +
LDS r24,0x0060    ' Bitcontainer -> R24
 +
ORI r24,0x40      ' setzen BIT1  ( .6) 
 +
STS 0x0060,r24    ' store container
 +
JMP L_0x00D2    ' --> END IF
 +
 
 +
' ------ ELSE
 +
L_0x00C8:
 +
LDS r24,0x0060    ' Bitcontainer -> R24
 +
ANDI r24,0xBF      ' löschen BIT1  ( .6) 
 +
STS 0x0060,r24    ' store container
 +
 
 +
' ------ END IF
 +
L_0x00D2:
 +
</pre>
 +
 
 +
==PORT-BIT Abfrage ==
 +
'''Ein Beispiel'''
 +
*Bascom
 +
<pre>
 +
  If Pinb.3 = 1 Then
 +
      Portb.4 = 1
 +
  Else
 +
      Portb.4 = 0
 +
  End If
 +
</pre>
 +
Bascom macht das erstaunlich kompliziert.
 +
 
 +
Erstmal wird R16 auf NULL gesetzt
 +
<pre>
 +
CLR r16 ' R16 = 0
 +
</pre>
 +
Dann PINB --> R24
 +
<pre>
 +
CLR XH
 +
LDI XL,0x36 ' PINB addr
 +
LD r24,X          ' PINB -> R24
 +
</pre>
 +
Nun PINB.3 --> T-Bit
 +
<pre>
 +
BST r24,3 '
 +
</pre>
 +
Ist das T-Bit = 1, wird R16 auf 1 korrigiert
 +
<pre>
 +
BRTC L_0x012E '
 +
LDI r16,0x01 '
 +
L_0x012E:
 +
</pre>
 +
Abhängig von R16 wird nun das Out-Port gesetzt oder gelöscht.
 +
<pre>
 +
LDI r20,0x01 ' R20 = &H01
 +
CP r16,r20 ' R16 <> R20
 +
BREQ L_0x0138 ' equal-> Setzen
 +
JMP L_0x013E ' not equal-> löschen
 +
L_0x0138:
 +
SBI PORTB,PB4 ' PORTB.4 = 1  (PORTB.PB4)
 +
JMP L_0x0140
 +
L_0x013E:
 +
CBI PORTB,PB4 ' PORTB.4 = 0 
 +
L_0x0140:
 +
</pre>
 +
 
 +
==BIT Set & Clear==
 +
Um einzelne Bits in einer Variablen zu setzen oder zu löschen, kann die Bitnummer auch als Variable angegeben werden.
 +
'''Ein Beispiel'''
 +
*Bascom
 +
<pre>
 +
Dim A As Byte
 +
Dim B As Byte
 +
Dim V1 As Byte
 +
Dim V2 As Byte
 +
 
 +
      V1 = 3
 +
      V2 = 6
 +
      B.v2 = 1
 +
      A.v1 = B.v2
 +
</pre>
 +
 
 +
*V1 = 3
 +
<pre>
 +
        LDI r24,0x03          ' = 3
 +
STS 0x0062,r24        'Adresse von "V1"
 +
</pre>
 +
*V2 = 6
 +
<pre>
 +
LDI r24,0x06          ' = 6
 +
STS 0x0063,r24        'Adresse von "V2"
 +
</pre>
 +
 
 +
*B.v2 = 1
 +
<pre>
 +
SET                    ' set T-Bit
 +
 
 +
LDI XL,0x63        ' adresse "V2"
 +
LDI XH,0x00
 +
LD r24,X       ' der Wert von V2 (=6)
 +
 
 +
LDI XL,0x61       ' Adresse der Variablen "B"
 +
LDI XH,0x00
 +
CALL L_0x010A      ' Adresse ausrichten
 +
 
 +
CALL L_0x00F6      ' Bit-Nummer in R24  auf Maske R24 und ~Maske
 +
 
 +
LD r0,X          ' Aktuelles Byte aus der Variablen
 +
BRTS L_0x00A2      ' T-BIT  ( setzen oder löschen )
 +
AND r0,r25        ' löschen
 +
RJMP L_0x00A4   
 +
L_0x00A2:
 +
OR r0,r24        ' setzen
 +
L_0x00A4:
 +
ST X,r0          ' Byte speichern
 +
</pre>
 +
 
 +
 
 +
*A.v1 = B.v2
 +
<pre>
 +
'------------------------------------------------
 +
' Das Bit B.V2  ins T-Bit übertragen
 +
'------------------------------------------------
 +
LDI XL,0x63
 +
LDI XH,0x00
 +
LD r24,X
 +
LDI XL,0x61
 +
LDI XH,0x00
 +
CALL L_0x010A      ' Adresse von "B" aurichten
 +
CALL L_0x00F6      ' BitNummer zu Maske
 +
LD r0,X          ' aktuelles Byte
 +
AND r0,r24        ' T-Bit übernimmt das Bit
 +
SET
 +
BRNE L_0x00C2
 +
CLT
 +
L_0x00C2:
 +
 
 +
'------------------------------------------------
 +
' Das T-Bit  ins Bit A.V1  übertragen
 +
'------------------------------------------------
 +
LDI XL,0x62
 +
LDI XH,0x00
 +
LD r24,X
 +
LDI XL,0x60
 +
LDI XH,0x00
 +
CALL L_0x010A      ' Adresse von "A" aurichten
 +
CALL L_0x00F6      ' BitNummer zu Maske
 +
LD r0,X          ' aktuelles Byte
 +
BRTS L_0x00DC      ' T-Bit ?
 +
AND r0,r25        ' löschen
 +
RJMP L_0x00DE
 +
L_0x00DC:
 +
OR r0,r24        ' setzen
 +
L_0x00DE:
 +
ST X,r0          'Speichern "A"
 +
</pre>
 +
 
 +
 
 +
*Bitnummer zu BitMaske und invertierter Maske
 +
<pre>
 +
L_0x00F6:
 +
LDI r25,0x01
 +
AND r24,r24
 +
BREQ L_0x0104
 +
CLC
 +
L_0x00FE:
 +
ROL r25
 +
DEC r24
 +
BRNE L_0x00FE
 +
L_0x0104:
 +
MOV r24,r25
 +
COM r25
 +
RET
 +
</pre>
 +
 
 +
*Variablen X = Adresse + (R24 / 8) und R24 = R24 Modulo 8
 +
<pre>
 +
L_0x010A:
 +
CPI r24,0x08
 +
BRLO L_0x0114
 +
ADIW XL,0x0001
 +
SUBI r24,0x08
 +
RJMP L_0x010A
 +
L_0x0114:
 +
RET
 +
</pre>
 +
 
 +
==PULSEIN==
 
  DIM Result AS WORD
 
  DIM Result AS WORD
 
  PULSEIN Result , Pind , 2 , 1
 
  PULSEIN Result , Pind , 2 , 1
 
*Aufruf
 
*Aufruf
 
<pre>
 
<pre>
LDI ZL,0x30        // Adresse von SFR PIND
+
LDI ZL,0x30        ' Adresse von SFR PIND
LDI r24,0x02 // PinNr 2
+
LDI r24,0x02 ' PinNr 2
LDI r16,0xFF        // State 1
+
LDI r16,0xFF        ' State 1
  
 
CALL PULSEIN
 
CALL PULSEIN
  
LDI XL,0x60       laden der Ergebnisadresse   
+
LDI XL,0x60         ' laden der Ergebnisadresse   
 
LDI XH,0x00
 
LDI XH,0x00
ST X+,r24 // store result
+
ST X+,r24 ' store result
 
ST X,r25
 
ST X,r25
 
</pre>
 
</pre>
Zeile 80: Zeile 353:
 
*Idle Loop
 
*Idle Loop
 
<pre>
 
<pre>
 +
'-----------------------------------------------------
 +
' der Wert in ZL:ZH wir auf Null heruntergezählt, dann geht's zurück
 +
'-----------------------------------------------------
 
L_0x009C:
 
L_0x009C:
 
SBIW ZL,0x0001
 
SBIW ZL,0x0001
Zeile 86: Zeile 362:
 
</pre>
 
</pre>
 
*Set_ErrBit
 
*Set_ErrBit
 +
Zur Erinnerung:  Das Err-Bit ist das, was man mit  "IF ERR = 1 THEN.." abfragt
 
<pre>
 
<pre>
SET
+
SET                     ' Setzen T-Bit
BLD r6,2
+
BLD r6,2            ' nach R6.2
 
RET
 
RET
 
</pre>
 
</pre>
 
*Clear_ErrBit
 
*Clear_ErrBit
 
<pre>
 
<pre>
CLT
+
CLT                     ' Löschen T-Bit
BLD r6,2
+
BLD r6,2            ' nach R6.2
 
RET
 
RET
 
</pre>
 
</pre>
 
*MakeMask
 
*MakeMask
 
<pre>
 
<pre>
LDI r25,0x01
+
'-----------------------------------------------------
AND r24,r24
+
' Eine Bit-Nummer in r24 (0-7) wird in eine Bit-Maske umgewandelt (1,2,4,...128)
BREQ L_0x00BC
+
' nach r25 kommt dann die invertierte Maske
CLC
+
'-----------------------------------------------------
 +
LDI r25,0x01             ' Maske = &B0000001
 +
AND r24,r24             ' BitNummer = 0 ?
 +
BREQ L_0x00BC             ' Ja, fertig
 +
CLC                         ' löschen Carry
 
L_0x00B6:
 
L_0x00B6:
ROL r25
+
ROL r25                 ' Maske eins nach links
DEC r24
+
DEC r24                 ' Bitnummer runterzählen
BRNE L_0x00B6
+
BRNE L_0x00B6             ' nochmal
 
L_0x00BC:
 
L_0x00BC:
MOV r24,r25
+
MOV r24,r25             ' Maske kopieren nach r24
COM r25
+
COM r25                 ' und r25 invertieren
RET
+
RET                         ' that's it.
 
</pre>
 
</pre>
 
*Pulsein, die eigentliche Funktion
 
*Pulsein, die eigentliche Funktion
Zeile 119: Zeile 400:
 
CLR XL // clear Timout Lo
 
CLR XL // clear Timout Lo
 
CLR XH // clear Timout Hi
 
CLR XH // clear Timout Hi
CALL MakeMask     // R24 Mask, R25 neg Mask
+
CALL MakeMask       // R24 Mask, R25 neg Mask
 
AND r16,r24
 
AND r16,r24
 
LDD r0,Z + 1 // DDRD
 
LDD r0,Z + 1 // DDRD
 
AND r0,r25 // Make Input
 
AND r0,r25 // Make Input
 
STD Z + 1,r0 // DDRD
 
STD Z + 1,r0 // DDRD
 +
'---------------------------------------------------------------------------
 +
' Warten, bis der Pin eine Zustand einnimmt, der anders ist als der, den man
 +
' messen will. Sprich, will ich die Dauer eines HIGH Zustandes messen, wartet
 +
' die Funktion erstmal, bis ein LOW anliegt. Eigentlich logisch.
 +
' Dabei wir das Registerpaar XL:XH von 0-65535 hochgezählt. Bei Überlauf wird
 +
' Bascom ERR gesetzt und abgebrochen.
 +
' Dieses Zählen ist aber nicht getimed, d.h. mit einem schnellen Quartz geht
 +
' es auch schneller, bzw. das Timeout ist kürzer.
 +
'---------------------------------------------------------------------------
 
L_0x00D8: // ------------- Loop
 
L_0x00D8: // ------------- Loop
 
LDD r0,Z + 0 // PIND
 
LDD r0,Z + 0 // PIND
Zeile 132: Zeile 422:
 
BREQ L_0x0118 // elapsed->ERR-Exit
 
BREQ L_0x0118 // elapsed->ERR-Exit
 
RJMP L_0x00D8 // cont'd Loop
 
RJMP L_0x00D8 // cont'd Loop
 +
'---------------------------------------------------------------------------
 +
' Und jetzt wird gewartet, bis der Pin den zu messenden Zustand einnimmt. Im Idealfall
 +
' ist das genau die Flanke, garantieren kann man das aber nicht.
 +
' Auch hier der gleiche "Time"out zähler wie oben.
 +
'---------------------------------------------------------------------------
 
L_0x00E6:
 
L_0x00E6:
 
CLR XL // clear Timout Lo
 
CLR XL // clear Timout Lo
Zeile 143: Zeile 438:
 
BREQ L_0x0118 // elapsed->ERR-Exit
 
BREQ L_0x0118 // elapsed->ERR-Exit
 
RJMP L_0x00EA // cont'd Loop
 
RJMP L_0x00EA // cont'd Loop
 +
'---------------------------------------------------------------------------
 +
' jetzt wird's ernst. Jetzt wird in 10µS Intervallen der Impuls HIGH (od.LOW)
 +
' gemessen. Das Ergebnis also 0 - 65535 * 10 µS. Das reicht etwa für eine 1/2
 +
' Sekunde. Gibt's eine Überlauf, wird ERR gesetzt.
 +
'---------------------------------------------------------------------------
 
L_0x00F8:
 
L_0x00F8:
 
CLR XL // clear Timout Lo
 
CLR XL // clear Timout Lo
 
CLR XH // clear Timout Hi
 
CLR XH // clear Timout Hi
 
 
L_0x00FC:
 
L_0x00FC:
 
PUSH ZL // Save
 
PUSH ZL // Save
 
PUSH ZH
 
PUSH ZH
LDI ZL,0x20 // calc from $XTAL
+
LDI ZL,0x20 // Dieser Wert wurde aus $CRYSTAL berechnet
 
LDI ZH,0x00
 
LDI ZH,0x00
 
CALL L_0x009C // 10 µS Idle
 
CALL L_0x009C // 10 µS Idle
Zeile 156: Zeile 455:
 
POP ZL
 
POP ZL
 
LDD r0,Z + 0 // PIND
 
LDD r0,Z + 0 // PIND
AND r0,r24 // PIND & Mask
+
AND r0,r24 // Maskieren des PIND.x
 
EOR r0,r16 // (PIND & Mask) ^ State  
 
EOR r0,r16 // (PIND & Mask) ^ State  
BRNE L_0x011C // OK, Pulsein done *********
+
BRNE L_0x011C // OK, die Funktion ist fertig
 
ADIW XL,0x0001 // PulseCounter++
 
ADIW XL,0x0001 // PulseCounter++
BRNE L_0x00FC // cont'd Loop
+
BRNE L_0x00FC // und weiter
 
L_0x0118:
 
L_0x0118:
CALL Set_ErrBit
+
CALL Set_ErrBit     // Irgendein Überlauf ist eingetreten
 
L_0x011C:
 
L_0x011C:
MOV r24,XL // result --> R24:r25
+
MOV r24,XL // Ergebnis --> R24:r25
 
MOV r25,XH
 
MOV r25,XH
RET // that's it
+
RET // Zurück
 
</pre>
 
</pre>
===ENCODER===
+
 
 +
==(Quadratur-) ENCODER==
 +
"Result" sollte man in Ruhe lassen oder nur lesen, es wird immer gebraucht, um den aktuellen Zustand der A / B Pins für den nächsten Check zu speichern.
 +
 
 +
Und noch ein Tip: Beim ersten Aufruf kommt in den meisten Fällen was Falsches raus, da ja noch kein gültiger "alter" Wert existiert. Also alle Encoder ersteinmal nur abfragen, aber nix zählen. Ab dann stimmt die Sache erst wirklich.
 +
 
 
*Aufruf
 
*Aufruf
 
<pre>
 
<pre>
Zeile 182: Zeile 486:
 
*Code
 
*Code
 
<pre>
 
<pre>
LDI XL,0x60 ; result
+
LDI XL,0x60 ' Ergebnis (s.o)
 
LDI XH,0x00
 
LDI XH,0x00
CLT ; clear t-bit  (no wait)
+
CLT ' clear t-bit  (nicht warten)
 
L_0x008C:
 
L_0x008C:
LD r20,X ; Lesen Result (vorheriger Pin-Wert)
+
LD r20,X ' Lesen Result (vorheriger Pin-Wert)
IN r16,PIND
+
IN r16,PIND               ' lesen PIN (d)
CLR r24 ; clear
+
CLR r24 ' Temp-Register clear
SBRC r16,1 ; pind.1
+
SBRC r16,1 ' pind.1 (A) ("skip-if-bit-clear")
ORI r24,0x01 ; A = 1
+
ORI r24,0x01 ' R24 OR 1
SBRC r16,3 ; pind.3
+
SBRC r16,3 ' pind.3 (B)
ORI r24,0x02 ; B = 2
+
ORI r24,0x02 ' R24 OR 2
CP r24,r20 ; <> previous
+
CP r24,r20 ' Vergleich mit altem Wert
BRNE L_0x00A2 ; difference
+
BRNE L_0x00A2 ' Da gab's eine Änderung
BRTS L_0x008C ; t-bit ? ( wait)
+
BRTS L_0x008C ' t-bit ? Wenn gesetzt, dann warten wir
RJMP L_0x00C2 ; no diff, no wait -> xit
+
RJMP L_0x00C2 ' no diff, no wait -> no job --> exit
 +
'------------------------------------------------------------------------------
 +
' das Register r24 hat nun den Level A/B auf den Bits 0 u. 1
 +
' &B000000AB 
 +
'------------------------------------------------------------------------------
 
L_0x00A2:
 
L_0x00A2:
ST X,r24 ; store new as old
+
ST X,r24 ' speichern für's nächste Mal
SWAP r20 ; OLD
+
SWAP r20 ' den alten Werte "swappen"
ADD r24,r20 ; OLD + NEW
+
'------------------------------------------------------------------------------
 +
' das Register r20 hat nun den (alten) Level A/B auf den Bits 4 u. 5
 +
' &B00ab0000 
 +
'------------------------------------------------------------------------------
 +
ADD r24,r20 ' OLD + NEW
 +
'------------------------------------------------------------------------------
 +
' jetzt stehen in r24 alter UND neuer Wert
 +
' &B00ab00AB
 +
'------------------------------------------------------------------------------
 +
' Aus Alt & Neu läßt sich eindeutig aus allen 4 Phasen die Drehrichtung ermitteln
 +
' Bascom checkt nur explizit auf die eine Richtung. Denn wenn's die nicht ist, muß es
 +
' ja wohl die andere sein.
 +
'------------------------------------------------------------------------------
 +
' Achtung: daraus folgt: wenn die Impulse zu schnell kommen, stimmen
 +
' die Ergebnisse u.U. NICHT !
 +
' Was vielleicht schlimmer ist: Man merkt es auch nicht
 +
'------------------------------------------------------------------------------
 
CPI r24,0x02
 
CPI r24,0x02
BREQ Leftlabel ; left
+
BREQ Leftlabel ' left
 
CPI r24,0x10
 
CPI r24,0x10
BREQ Leftlabel ; left
+
BREQ Leftlabel ' left
 
CPI r24,0x23
 
CPI r24,0x23
BREQ Leftlabel ; left
+
BREQ Leftlabel ' left
 
CPI r24,0x31
 
CPI r24,0x31
BREQ L_0x00BE ; left
+
BREQ L_0x00BE ' left
CALL Rightlabel ; call Right
+
CALL Rightlabel ' call Right
RJMP L_0x00C2 ; xit
+
RJMP L_0x00C2 ' xit
 
L_0x00BE:
 
L_0x00BE:
CALL Leftlabel ; call Left
+
CALL Leftlabel ' call Left
 
L_0x00C2:
 
L_0x00C2:
 
</pre>
 
</pre>
Zeile 218: Zeile 542:
 
<pre>
 
<pre>
 
Leftlabel:
 
Leftlabel:
RET
+
RETURN
 
Rightlabel:
 
Rightlabel:
RET
+
RETURN
 
</pre>
 
</pre>
 
Die Links-Rechts Routinen sind hier sparsam gehalten. Normalerweise wird man hier wohl Schritte zählen.
 
Die Links-Rechts Routinen sind hier sparsam gehalten. Normalerweise wird man hier wohl Schritte zählen.
 +
 +
==Dividieren 32Bit (LONG)==
 +
Das folgende Beispiel ist eigentlich eine Bascom-Source mit inline Assembler. Es zeigt, wie Bascom zwei signed LONG dividiert, und durch den Assembler kann man sich das auch im Simulator bitweise anschauen, wie das läuft.
 +
 +
Anmerkung: Nach der Division befindet sich der Divisionsrest in r16:r19. Wenn den jemand braucht, könnte er ihn nach einer long-Division dort abholen.
 +
 +
*Aufruf
 +
<pre>
 +
$regfile = "m32def.dat"                                    ' specify the used micro
 +
$crystal = 8000000
 +
 +
Dim Vala As Long
 +
Dim Valb As Long
 +
Dim Valc As Long
 +
 +
  Vala = 14
 +
  Valb = 1
 +
' das Bascom Äquivalent wäre:
 +
'      Valc = Vala / Valb
 +
$asm
 +
  Loadadr Vala , X                    ' Laden r16:r19 mit Vala
 +
  LD r16,X+
 +
  LD r17,X+
 +
  LD r18,X+
 +
  LD r19,X+
 +
  Loadadr Valb , X                    ' Laden r20:r23 mit Valb
 +
  LD r20,X+
 +
  LD r21,X+
 +
  LD r22,X+
 +
  LD r23,X+
 +
$end Asm
 +
 +
  Gosub L_0x0112                      ' Dividieren  r16:r19 / r20:r23 --> r20:r23
 +
 +
$asm
 +
  Loadadr Valc , X                    ' speichern Ergebnis in Valc
 +
  ST X+,r20
 +
  ST X+,r21
 +
  ST X+,r22
 +
  ST X+,r23
 +
$end Asm
 +
 +
End
 +
</pre>
 +
*Funktions-Code
 +
<pre>
 +
'-----------------------------------------
 +
'
 +
'-----------------------------------------
 +
L_0x0112:
 +
$asm
 +
  RCALL L_0x0174        'Operanden Vorzeichen prüfung und ggf. umdrehen auf positiv
 +
                        'register r0.0 zeigt, ob das Ergebnis negativ ist
 +
  RCALL L_0x0120        'Prüfen Divisor auf NULL. Wenn nicht, dann dividieren
 +
  BLD r0,1              't-bit -> r0.1 (Error)
 +
  RJMP L_0x010C        'finish
 +
 +
L_0x010C:
 +
  SBRC r0,0            'Ergebnis negativ ?
 +
  RCALL L_0x011A 'ja, Ergebnis negativ machen
 +
  RET                  'so oder so, Funktionsende
 +
 +
L_0x011A:
 +
  RCALL L_0x018A        'Ergebnis auf negativ (r20:r23)
 +
  RCALL L_0x019C        'Rest auf negativ (r16:r19)
 +
  RET
 +
 +
 +
'-----------------------------------------
 +
' Prüfen divisor auf NULL
 +
'-----------------------------------------
 +
L_0x0120:
 +
  MOV r24,r20                'Kopieren r20:r23 -> r24:r27
 +
  MOV r25,r21
 +
  MOV r26,r22
 +
  MOV r27,r23
 +
  CLT       ' clear T-bit
 +
  !OR r20,r21                ' prüfen auf NULL
 +
  !OR r20,r22
 +
  !OR r20,r23
 +
  BRNE L_0x0136              ' <> NULL --> dividieren
 +
  !SET                        ' ==NULL set t-Bit
 +
  RET
 +
'-----------------------------------------
 +
' dividieren r16:r19 / r20:r23  --> r20:r23
 +
'-----------------------------------------
 +
L_0x0136:
 +
  MOV r20,r16                'r16:r19->r20:r23
 +
  MOV r21,r17
 +
  MOV r22,r18
 +
  MOV r23,r19
 +
  CLR r16                    'clear r16:r19
 +
  CLR r17
 +
  CLR r18
 +
  CLR r19
 +
 +
  LDI ZH,32                  'Zähler  (32 Bit)
 +
'-----------------------------------------
 +
' divisions-Schleife
 +
'-----------------------------------------
 +
L_0x0148:
 +
  LSL r20                    'shift left r20:r23
 +
  ROL r21                 
 +
  ROL r22
 +
  ROL r23                    '(das oberste Bit wandert nach r16:r19)
 +
 +
  ROL r16                    'shift left r16:r19
 +
  ROL r17
 +
  ROL r18
 +
  ROL r19
 +
 +
  !SUB r16,r24                'subtrahieren
 +
  SBC r17,r25
 +
  SBC r18,r26
 +
  SBC r19,r27
 +
 +
  ORI r20,1                  'Ergebnis auf 1
 +
  BRCC L_0x016E              'kein Überlauf, bleibt so
 +
 +
  ADD r16,r24                'Überlauf, wieder addieren
 +
  ADC r17,r25
 +
  ADC r18,r26
 +
  ADC r19,r27
 +
  ANDI r20,254              'und den Ergebnis 1-er wieder löschen
 +
L_0x016E:
 +
  DEC ZH                    'Zähler--
 +
  BRNE L_0x0148              '23-Bit Schleife
 +
  RET                        'fertig
 +
 +
'-----------------------------------------
 +
' Prüfen der Operanden
 +
'-----------------------------------------
 +
L_0x0174:
 +
  CLR r0                    'Kontroll-Register säubern
 +
  CLT                        't-bit löschen
 +
  SBRS r23,7                ' r20:r23 Negativ ?
 +
  RJMP L_0x0180              'nö, ist positiv
 +
  RCALL L_0x018A            'ja, auf positiv umdrehen
 +
  !SET                      'und t-bit setzen
 +
L_0x0180:
 +
  BLD r0,0                  't-bit ins Kontroll-register r0.0
 +
  RCALL L_0x0196       'Prüfen r16:r19
 +
  BLD r1,0       't-bit -> r1.0
 +
  EOR r0,r1       'Exklusiv Or der beiden +- Prüfungen
 +
                              'Vorzeichen verschieden ---> Ergebnis muss negativ sein
 +
  RET                        'e bien
 +
'-----------------------------------------
 +
' r20:r23 auf positiv
 +
'-----------------------------------------
 +
L_0x018A:
 +
  RCALL L_0x01B0            'invertieren
 +
  SUBI r20,0xFF              ' +1
 +
  SBCI r21,0xFF
 +
  SBCI r22,0xFF
 +
  SBCI r23,0xFF
 +
  RET
 +
'-----------------------------------------
 +
' Prüfen  r16:r19
 +
'-----------------------------------------
 +
L_0x0196:
 +
  CLT                        'clear t-bit
 +
  SBRS r19,7                'sign bit set ?
 +
  RET                        'no
 +
'-----------------------------------------
 +
' r16:r19 auf positiv
 +
'-----------------------------------------
 +
L_0x019C:
 +
  COM r16                  'invertieren
 +
  COM r17
 +
  COM r18
 +
  COM r19
 +
  SUBI r16,0xFF            '+1
 +
  SBCI r17,0xFF
 +
  SBCI r18,0xFF
 +
  SBCI r19,0xFF
 +
  !SET                    't-bit setzen
 +
  RET
 +
'-----------------------------------------
 +
'  invertieren r20:r23
 +
'-----------------------------------------
 +
L_0x01B0:
 +
  COM r20
 +
  COM r21
 +
  COM r22
 +
  COM r23
 +
  RET
 +
 +
$end Asm
 +
 +
Return
 +
</pre>
 +
==Multiplizieren 32Bit (LONG)==
 +
Hier ist ein Unterschied, ob der µC über den MUL-Befehl verfügt.
 +
===Mit "MUL" (Atmega32)===
 +
<pre>
 +
MOVW r24,r16        'r16:r19 -> r24:r27
 +
MOVW r26,r18
 +
 +
MUL r20,r24        'r20 * r24
 +
MOVW r16,r0          '-> r16:r17
 +
 +
MUL r20,r26 'r20 * r26
 +
MOVW r18,r0          '-> r18:r19
 +
 +
MUL r20,r27 'r20 * r27
 +
MOV r2,r0 '--> r2
 +
 +
MUL r20,r25 'r20 * r25
 +
ADD r17,r0 'r17:r18:r19 + r0:r1:r2
 +
ADC r18,r1
 +
ADC r19,r2
 +
 +
MUL r21,r26 'r21 * r26
 +
MOV r2,r0
 +
MUL r21,r24 'r21 * r26
 +
ADD r17,r0 'r17:r18:r19 + r0:r1:r2
 +
ADC r18,r1
 +
ADC r19,r2
 +
 +
MUL r21,r25 'r21 * r26
 +
ADD r18,r0 'r18:r19 + r0:r1
 +
ADC r19,r1
 +
 +
MUL r22,r24 'r22 * r26
 +
ADD r18,r0 'r18:r19 + r0:r1
 +
ADC r19,r1
 +
 +
MUL r22,r25 'r22 * r26
 +
ADD r19,r0 'r19 + r0
 +
 +
MUL r23,r24 'r23 * r26
 +
ADD r19,r0 'r19 + r0
 +
 +
RET
 +
</pre>
 +
 +
===Ohne "MUL" (AT90S2313)===
 +
Hier wird "zu Fuß" multipliziert. Das Vorbereiten der Operanden ist wie beim Dividieren (Prüfung auf NULL entfällt natürlich), auch die Korrektur der Ergebnisses ist genauso (Ergebnis ist diesmal aber in R16:r19).
 +
 +
Ich beschränke mich daher auf das Listing der eigentlichen Multiplikation der beiden 32-Bit Zahlen R16:r19 * r20:r23 ---> r16:r19
 +
<pre>
 +
'------------------------------------------
 +
'
 +
'------------------------------------------
 +
MOV r24,r20      'r20:r23 ---> r24:r27
 +
MOV r25,r21
 +
MOV r26,r22
 +
MOV r27,r23
 +
CLR r20          'Löschen r20:r23
 +
CLR r21
 +
CLR r22
 +
CLR r23
 +
LDI ZH,0x21      'Zähler 31 (Überlauf nach 32 Bit wird nicht berücksichtigt)
 +
CLC
 +
RJMP L_0x00B6    'Einstieg in die Schleife
 +
'------------------------------------------
 +
'  Schleife
 +
'------------------------------------------
 +
L_0x00A4:
 +
BRCC L_0x00AE   
 +
ADD r20,r24      ' Nur bei Carry wird addiert
 +
ADC r21,r25
 +
ADC r22,r26
 +
ADC r23,r27
 +
L_0x00AE:
 +
LSR r23          'shift right r20:r23
 +
ROR r22
 +
ROR r21
 +
ROR r20          ' Carry -> r16:r19
 +
 +
L_0x00B6:                    'Einsprungs-Stelle
 +
ROR r19          'shift right r16:r19
 +
ROR r18
 +
ROR r17
 +
ROR r16
 +
DEC ZH          'Zähler
 +
BRNE L_0x00A4    'Schleife <> 0
 +
RET                  'fertig
 +
</pre>
 +
==SERIN==
 +
SERIN (und SEROUT) stellen die allgemeinste Form der Software UART dar. Es kann zur Run-Time von einem Aufruf zum nächsten so ziemlich alles verändert werden: PIN, Polarität, Baudrate, Stopp-Bits, Databits (7 od.8 ) und Parity. Anders als bei der normalen SW-UART kann der gleiche Pin zum Senden und Empfangen werden, was für einige half-duplex Kommunikationsformen sehr praktisch ist. Es kann aber nur entweder empfangen oder gesendet werden, eine Bus-Arbitrierung ist also nicht möglich.
 +
 +
Diese Variationsmöglichkeiten haben ihren Preis. Insbesonders die Berechnung der Delay-Counter für die Baudrate, die bei jedem Aufruf neu geschieht, tut weh.
 +
 +
Aber wir wollen hier auch keine Urteile abgeben, sondern nur "sine ira et studio" die Dinge zeigen, wie sie sind. Zum daraus lernen (und ev. dann besser machen) reicht es immer.
 +
 +
A propos: Beim Empfangen wird "parity" zwar akzeptiert, geprüft wird das aber nicht
 +
 +
*Aufruf
 +
<pre>
 +
$crystal = 8000000
 +
Dim Mystring As String * 10
 +
Dim Mybaud As Long
 +
 +
  Mybaud = 9600
 +
 +
  Serin Mystring , 0 , D , 0 , Mybaud , 0 , 8 , 1
 +
</pre>
 +
Zur Bedeutung der Parameter bitte Bascom-Help verwenden. Nur soviel: es wird unterschieden, ob die Empfangs-variable ein String ist oder nicht. Wenn ja, läuft die Routine, bis ein <CR> empfangen wird, ansonsten muß eine Länge angegeben werden.
 +
 +
 +
Zur Berechnung der Schleifenzähler werden die internen 32-Bit Funktionen verwendet, die hatten wir schon, also laß ich sie hier weg.
 +
*Code
 +
Vorbereiten der Parameter
 +
<pre>
 +
LDI XL,0x6B      'Adresse von "MyBaud"
 +
LDI XH,0x00
 +
LD r16,X+        ' Baudrate --> r16:r19
 +
LD r17,X+
 +
LD r18,X+
 +
LD r19,X
 +
 +
LDI XL,0x6F      ' next free SRAM ("___SER_BAUD") (LONG)
 +
LDI XH,0x00
 +
ST X+,r16        ' speichern der Baudrate
 +
ST X+,r17
 +
ST X+,r18
 +
ST X,r19
 +
 +
CALL L_0x0374 'calc counter nach "___SER_BAUD"
 +
'---------------------------------------------------------------------------
 +
'Bascom berechnet:  Counter =  (( $CRYSTAL / Baudrate ) + 42 ) / 8
 +
'Das ist ein Schleifenzähler für die Dauer eines halben Bits
 +
'---------------------------------------------------------------------------
 +
LDI r24,0x01 '1 stopp-bit
 +
ST -Y,r24
 +
LDI r24,0x00 'no parity
 +
ST -Y,r24
 +
LDI r24,0x08 '8 Bit
 +
ST -Y,r24
 +
LDI XL,0x60 ' Datenempfangs-adresse ("MyString")
 +
LDI XH,0x00
 +
CLR r24            ' Count =null, weil bei string auf <CR> gewartet wird
 +
ST -Y,r24
 +
LDI ZL,0x31 ' Adresse von  DDRD für PinD
 +
LDI r24,0x00        ' Pin # 0 
 +
CLT ' Polarität
 +
CALL L_0x02BE ' CALL der eigentlichen "SERIN" Funktion
 +
</pre>
 +
 +
*Die Funktion
 +
<pre>
 +
'-----------------------------------------
 +
'SERIN
 +
'-----------------------------------------
 +
L_0x02BE:
 +
RCALL MakeMask ' Pin-# nach BitMask und !BitMask (siehe PULSEIN)
 +
CLR ZH              '
 +
 +
LDD r0,Z + 0 ' adresse von DDRD
 +
AND r0,r25 ' Pin wird Input
 +
STD Z + 0,r0
 +
 +
INC ZL ' jetzt zeigt Z auf PORTD
 +
 +
LDD r0,Z + 0        ' Clear PORTD.x
 +
AND r0,r25
 +
STD Z + 0,r0
 +
 +
SBIW ZL,0x0002      ' jetzt zeigt Z auf PIND
 +
 +
MOV r16,r24 ' Mask
 +
MOV r22,r25 ' !Mask
 +
 +
LD r20,Y+          ' Byte count 
 +
MOV r19,r20
 +
'-------------------------------------------------------------------------
 +
' Ausrechnen der effektive Framesize  (Data-Bits, Parity, Stopp-Bits)
 +
'-------------------------------------------------------------------------
 +
L_0x02DA:
 +
LDD r17,Y + 0 ' Data-Bits (7/8)
 +
CPI r17,0x07 ' 7 Bit ?
 +
BRNE L_0x02F2 ' nein, es sind 8
 +
'---------------------------------------------------
 +
' 7 Data-Bits
 +
'---------------------------------------------------
 +
LDD r17,Y + 1 ' parity
 +
CPI r17,0x00        ' Y/N
 +
BRNE L_0x02EC        ' Y
 +
LDD r18,Y + 2      ' stopp-bits
 +
SUBI r18,0xF9 ' +7    (0xF9 == -7 )
 +
RJMP L_0x0302       
 +
L_0x02EC:
 +
LDD r18,Y + 2 ' stopp-bits
 +
SUBI r18,0xF8 ' +8    (0xF8 == -8 )
 +
RJMP L_0x0302
 +
'---------------------------------------------------
 +
' 8 Data-Bits
 +
'---------------------------------------------------
 +
L_0x02F2:
 +
LDD r17,Y + 1 ' parity
 +
CPI r17,0x00        ' Y/N
 +
BRNE L_0x02FE ' Y
 +
LDD r18,Y + 2 ' stopp-bits
 +
SUBI r18,0xF8 ' +8    (0xF8 == -8 )
 +
RJMP L_0x0302
 +
L_0x02FE:
 +
LDD r18,Y + 2 ' stopp-bits
 +
SUBI r18,0xF7 ' +9    (0xF7 == -9 )
 +
 +
L_0x0302:
 +
MOV r21,r18        ' Framesize
 +
'----------------------------------------
 +
' Der eigentlich BYTE-Read-Loop
 +
'----------------------------------------
 +
' R18 /R21 frame bits 
 +
' X data pointer 
 +
' Z PIN(d) 
 +
' T-Bit revert polarity
 +
' Y Parameter 
 +
' r20 Laufender Byte-Zähler
 +
' r19 gewünschte Anzahl Bytes (bei einem String ist das NULL)
 +
'----------------------------------------
 +
 +
 +
'----------------------------------------
 +
' Die fallende(od. steigende) Flanke des Start-Bits finden
 +
'----------------------------------------
 +
L_0x0304:
 +
LDD r0,Z + 0 ' Rx Pin
 +
AND r0,r16 ' mask
 +
BRTS L_0x030E ' T-Bit ?
 +
BREQ L_0x0312 ' Low--> start
 +
RJMP L_0x0304 ' wait loop
 +
 +
' reverse polarity
 +
L_0x030E: 
 +
BRNE L_0x0312 ' high--> start
 +
RJMP L_0x0304 ' wait loop
 +
 +
'----------------------------------------
 +
'StartBit Flanke gefunden, bis zur ~Mitte dauert es noch 1/2 Bit
 +
'----------------------------------------
 +
L_0x0312:
 +
RCALL L_0x035E ' Warten 1/2 Bit
 +
'----------------------------------------
 +
' Data Bit Loop
 +
'----------------------------------------
 +
L_0x0314:
 +
RCALL L_0x035E ' Warten 1/2 Bit
 +
RCALL L_0x035E ' Warten 1/2 Bit
 +
 +
CLC ' clear carry
 +
LDD r0,Z + 0 ' Lesen PIN
 +
AND r0,r16 ' Maskieren Bit
 +
BREQ L_0x0322 ' Bit ist Low
 +
SEC ' Bit ist High --> set carry
 +
L_0x0322:
 +
DEC r18 ' Frame Bit-Zähler
 +
BREQ L_0x032C ' fertig
 +
ROR r24 ' das Carry reinschieben nach r24:r25
 +
ROR r25
 +
RJMP L_0x0314 ' und weiter
 +
 +
L_0x032C:
 +
CPI r21,0x0A ' Framesize  <> 10 ?
 +
BRLO L_0x033A ' lower
 +
SUBI r21,0x09 ' >= 10 --> sub 9
 +
L_0x0332:
 +
ROL r25 ' Ausrichten Data Byte
 +
ROL r24
 +
DEC r21            '
 +
BRNE L_0x0332 ' Zählschleife
 +
L_0x033A:
 +
BRTC L_0x033E        ' Polarität ?
 +
COM r24 ' Umdrehen data Byte
 +
L_0x033E:
 +
LDD r17,Y + 0      ' Data-Bits
 +
CPI r17,0x07
 +
BRNE L_0x0346        ' = 8
 +
ANDI r24,0x7F ' = 7 , also löschen 2^^7
 +
L_0x0346:
 +
ST X+,r24 ' speicher DataByte
 +
CPI r19,0x00 ' soll es ein string werden ?
 +
BRNE L_0x0358 ' nein
 +
CPI r24,0x0D ' ja, ist das ein <CR>
 +
BRNE L_0x02DA ' nö, weiter in der Byte-Schleife
 +
CLR r24 ' Ja, in der Empfangs string wird aber statt dessen
 +
ST X,r24 ' 0x00 geschrieben, damit es ein Null-terminierter String wird
 +
L_0x0354:
 +
ADIW YL,0x0003 ' restore Y auf den Einstiegswert
 +
RET ' fertig
 +
L_0x0358:
 +
DEC r20 ' kein String, müssen wir also zählen
 +
BREQ L_0x0354 ' geschafft.
 +
RJMP L_0x02DA ' weiter geht's
 +
 +
'-------------------------------------------
 +
' 1/2 Bit Delay
 +
'-------------------------------------------
 +
L_0x035E:
 +
PUSH r24
 +
PUSH r25
 +
LDS r24,0x006F ' das LSB von "___SER_BAUD" (s.o.)
 +
LDS r25,0x0070 ' das MSB
 +
L_0x036A:
 +
SBIW r24,0x0001
 +
BRNE L_0x036A
 +
POP r25
 +
POP r24
 +
RET
 +
</pre>
 +
==SERVO==
 +
Damit können bis zu 16 Servos an beliebigen Port-Pins versorgt werden ([[Servoansteuerung|alternative Servoansteuerungsmöglichkeit]]). Bascom nimmt dazu einen Timer in Beschlag (defaultmäßig Timer0). Für zwei Servos (Beispiel) wird konfiguriert:
 +
Config Servos = 2 , Servo1 = Portb.0 , Servo2 = Portb.1 , Reload = 10
 +
"Reload" bestimmt die Auflösung, die Angabe "10" für 10µS wird empfohlen.
 +
 +
Bascom legt einige Felder an:
 +
DIM Counter AS WORD    '(verdeckt und nicht zugänglich, der Name ist nur zur Veranschaulichung)
 +
und
 +
DIM SERVO(2) AS BYTE  ' für zwei Servos, sonst eben mehr oder weniger
 +
In dies Byte schreibt man die gewünschte Servo-Impulsdauer (in 10µS Schritten). Und zwar nur dann, wenn sich was ändert, denn das refreshen ( alle 20mS) übernimmt Bascom völlig selbstätig.
 +
 +
Die Port-Pins muß man übrigens selbst auf "Output" setzen
 +
Config Pinb.0 = Output
 +
Config Pinb.1 = Output
 +
 +
*Bascom definiert eine ISR-Routine und setzt den Timer auf (TIMER0)
 +
 +
<pre>
 +
IN r24,TCCR0
 +
ORI r24,0x01 'no prescale (/1)
 +
OUT TCCR0,r24
 +
 +
IN r24,TIMSK
 +
ORI r24,0x01 'enable
 +
OUT TIMSK,r24
 +
 +
LDI r24,0xB1 'preload = 177
 +
OUT TCNT0,r24
 +
</pre>
 +
Diese Werte berechnet Bascom aus der "$Crystal=" Angabe. Es ergibt einen Interrupt jede 10µS (s.o).
 +
 +
*Das übrige Servo-Handling spielt sich ausschließlich in der ISR-Routine ab
 +
 +
<pre>
 +
'------------------------------------------------
 +
'
 +
'------------------------------------------------
 +
OVF0ADDR_ISR:
 +
PUSH r16
 +
PUSH r24
 +
IN r24,SREG
 +
PUSH r24
 +
PUSH XL
 +
PUSH XH
 +
PUSH r25
 +
 +
LDI XL,0x60 ' counter++
 +
LDI XH,0x00
 +
LD r24,X+
 +
LD r25,X
 +
ADIW r24,0x0001
 +
ST X,r25
 +
ST -X,r24
 +
 +
CLR r16
 +
CPI r24,0x01 ' count == 1 ?
 +
CPC r25,r16
 +
BRNE L_0x00C2
 +
 +
SBI PORTB,PB0 ' PORTB.0 = 1
 +
SBI PORTB,PB1 ' PORTB.1 = 1
 +
'........... eventuell noch andere Servos
 +
RJMP L_0x00E8 ' exit ISR
 +
 +
L_0x00C2:
 +
CPI r25,0x00 ' Msb r24:r25 == 0 ?
 +
BREQ L_0x00D6 ' yes -->
 +
 +
CPI r24,0xD0 ' r24:r25 == 2000 ?
 +
LDI r16,0x07
 +
CPC r25,r16
 +
BRLO L_0x00E8 ' LOW --> exit ISR
 +
 +
CLR r16 ' reset counter
 +
ST X+,r16
 +
ST X,r16
 +
RJMP L_0x00E8 'exit ISR
 +
 +
L_0x00D6:
 +
ADIW XL,0x0002
 +
 +
LD r25,X+ 'servo(1)
 +
CP r24,r25 'counter == ?
 +
BRLO L_0x00E0 'not yet
 +
CBI PORTB,PB0 'yes, clear Servo 1
 +
L_0x00E0:
 +
LD r25,X+ 'servo(2)
 +
CP r24,r25 'counter == ?
 +
BRLO L_0x00E8 'Zum nächsten Servo oder Exit-ISR
 +
CBI PORTB,PB1 'clear Servo 2
 +
 +
'........... eventuell noch andere Servos
 +
 +
L_0x00E8:
 +
LDI r24,0xB1
 +
OUT TCNT0,r24 ' reload Timer
 +
POP r25
 +
POP XH
 +
POP XL
 +
POP r24
 +
OUT SREG,r24
 +
POP r24
 +
POP r16
 +
RETI
 +
</pre>
 +
 +
*Erläuterung:  Im Kern wird hier bei jedem Interrupt ein 16-Bit Zähler 'raufgezählt.
 +
**Bei "Zähler = 2000" wird der Zähler wieder auf Null gesetzt und es geht von vorne los. Bei 10µS Interrupts dauert sowas eben ca 20 mS, das ist der Wert, den übliche Servos zum Refresh brauchen.
 +
**Bei "Zähler = 1"  werden alle Servo-Pins auf "1" gesetzt, d.h. die Impulse werden gestartet.
 +
**Solange "Zähler < 256", wird nach der Reihe der Zählerstand mit den Werten verglichen, die in dem SERVO(n) Array stehen. Ist der Zähler gleich oder größer, wird der jeweilige Impuls abgedreht. Bei einer Angabe "100" dauert das eben ca 1 mS, bei "200" 2 mS. Das ist der übliche Impuls-Bereich für Servos.
 +
**Solange "Zähler >= 256" wird nur mehr gezählt, bis es Zeit für einen Refresh ist (s.o).
 +
 +
==I2C Software==
 +
Für AVRs ohne Hardware-TWI bietet Bascom eine Reihe I2c Funktionen an. Die SDA & SCL Pins können beliebig gewählt werden.
 +
[[I2C|Hier gibt's nähere Info über I2C]]
 +
 +
<pre>
 +
Config I2cdelay = 5                  ' ergibt lt. "Help" ca 200 kHz
 +
Config Scl = Portc.0                  'Ports/Pins fuer I2C-Bus
 +
Config Sda = Portc.1
 +
 +
  I2cinit       
 +
  I2cstart
 +
  I2cwbyte Out_adr
 +
  I2cwbyte &H55
 +
  I2cstop
 +
</pre>
 +
 +
*I2cinit                                 
 +
<pre>
 +
CBI PORTC,PC1  'Output auf LOW  SDA
 +
CBI PORTC,PC0  'Output auf LOW  SCL
 +
CBI DDRC,DDC1  'Pin auf Input
 +
CBI DDRC,DDC0  'Pin auf Input
 +
RET
 +
</pre>
 +
Bascom setzt die SDA & SCL Pins auf "LOW". Gesteuert werden die dann nurmehr durch
 +
*setzen in DDRx auf Output, dadurch wird die Leitung auf NULL gezogen,
 +
*oder auf Input, dann zieht der Pullup-Widerstand die Leitung hoch.
 +
 +
 +
*Verzögerungs-Routine für einen 1/2 Bus-Cycle, aus "I2cdelay=" und "$Crystal=" berechnet.
 +
<pre>
 +
Idle_Loop:
 +
SBIW ZL,0x0001    '2    cyc.
 +
BRNE Idle_Loop    '1/2  cyc.
 +
RET                  '4    cyc.
 +
Idle_Call:
 +
PUSH ZL            '2    cyc.
 +
PUSH ZH            '2    cyc.
 +
LDI ZL,0x06      '1    cyc.      I2cdelay=5, $Crystal=8000000
 +
LDI ZH,0x00      '1    cyc.
 +
CALL Idle_Loop    '4    cyc.
 +
POP ZH            '2    cyc.
 +
POP ZL            '2    cyc.
 +
RET                  '4    cyc.
 +
</pre>
 +
Ganz klar komme ich mit der Schleife ja nicht. Wenn ich das nachrechne, verbraucht die Routine insgesamt 45 Cyclen, + 3 für einen "RCALL" auf die Routine, wären insgesamt 48 Cycles. Bei 8 MHZ braucht der AVR dafür offensichtlich 6 µS. Also, 200 kHz krieg' ich da dann nicht raus.
 +
 +
*I2cstart
 +
<pre>
 +
SBI DDRC,DDC0    ' SCL runter
 +
CBI DDRC,DDC1    ' SDA rauf
 +
RCALL Idle_Call    ' etwas warten
 +
CBI DDRC,DDC0    ' SCL rauf
 +
RCALL Idle_Call    ' wieder warten
 +
SBI DDRC,DDC1    ' SCL runter  (Startbedingung)   
 +
Idle_Call:
 +
        (s.o)                ' wieder warten
 +
</pre>
 +
 +
*I2cstop
 +
<pre>
 +
I2CSTOP:
 +
SBI DDRC,DDC0    ' SCL runter
 +
SBI DDRC,DDC1    ' SDA runter
 +
RCALL Idle_Call
 +
CBI DDRC,DDC0    ' SCL rauf
 +
L_0x00E4:
 +
SBIS PINC,PINC0  ' check "Stretching"
 +
RJMP L_0x00E4    ' noch nicht oben, nochmal
 +
RCALL Idle_Call    ' warten
 +
CBI DDRC,DDC1    ' SDA rauf  (Stoppbedingung)
 +
RJMP Idle_Call    ' wieder warten und tschüss
 +
</pre>
 +
 +
*I2cwbyte  (reg 17 = Data-Byte)
 +
<pre>
 +
SEC
 +
ROL r17          ' Jetzt ist das MSB im Carry, und ein 1-er ins R17 reingeschoben
 +
RJMP L_0x00F6    ' Schleifen-Einstieg
 +
L_0x00F4: ----------------- SCHLEIFE ---------------
 +
LSL r17
 +
L_0x00F6:
 +
BREQ L_0x0112    ' R17 == NULL ?  --> fertig
 +
SBI DDRC,DDC0    ' SCL runter
 +
BRCC L_0x0102    ' SDA Null oder EINS ?
 +
NOP
 +
CBI DDRC,DDC1    ' EINS, also SDA rauf
 +
RJMP L_0x0106
 +
L_0x0102:
 +
SBI DDRC,DDC1    ' NULL, also SDA runter
 +
RJMP L_0x0106
 +
L_0x0106:
 +
RCALL Idle_Call    ' warten
 +
CBI DDRC,DDC0    ' SCL  rauf
 +
L_0x010A:
 +
SBIS PINC,PINC0  ' "stretcht" da einer ?
 +
RJMP L_0x010A    ' ja
 +
RCALL Idle_Call    ' jetzt ist SCL wirklich oben, warten
 +
RJMP L_0x00F4    ' das nächste Bit
 +
 +
L_0x0112:
 +
SBI DDRC,DDC0    ' SCL runter für das ACK-Bit
 +
CBI DDRC,DDC1    ' SDA rauf, bzw. freigeben
 +
RCALL Idle_Call    ' warten
 +
CBI DDRC,DDC0    ' SCL rauf
 +
L_0x011A:
 +
SBIS PINC,PINC0  ' stretch ?
 +
RJMP L_0x011A   
 +
CLT                  ' löschen T-Bit
 +
SBIC PINC,PINC1  ' ACK oder nicht ?
 +
SET                  ' kein ACK, also T-Bit setzen
 +
BLD r6,2        ' so oder so, das T-Bit in das "ERR" Bit
 +
RJMP Idle_Call    ' warten (und fertig)
 +
</pre>
  
 
==Autor==
 
==Autor==
Zeile 228: Zeile 1.280:
  
 
==Siehe auch==
 
==Siehe auch==
 +
* [[Bascom]]
 
* [[Bascom Inside]]
 
* [[Bascom Inside]]
 +
* [[Bascom Libraries]]
 
[[Kategorie:Microcontroller]]
 
[[Kategorie:Microcontroller]]
 
[[Kategorie:Grundlagen]]
 
[[Kategorie:Grundlagen]]
 
[[Kategorie:Software]]
 
[[Kategorie:Software]]
 +
[[Kategorie:Quellcode Bascom]]
 +
[[Kategorie:Quellcode Assembler AVR]]

Aktuelle Version vom 23. Februar 2009, 20:23 Uhr

Für den allgemein Interessierten und für Power-User, die ihr Bascom-Programm mit etwas Assembler-Code aufpeppen möchten, stelle ich recht zwanglos einige Assembler-Codeschnipsel zusammen mit den zugehörigen Bascom-Statements zur Verfügung.

Gleich ein Hinweis: wer daran schrauben möchte, sollte auch bei den Bascom Libraries als Ergänzung zum Inline Assembler vorbeischauen,

Die zugrundeliegende Bascom-Version 1.11.8.1

Das Disassembling wurde erstellt mit dem PicNickHexHacker 1.0.x.y.z (Der AVR-Studio Disassembler ist mir zu unleserlich, vielleicht eine Alterserscheinung).

Die meisten Beispiele könnte man mit Inline-Assembler direkt ersetzen, wenn man mal probieren wollte, man muß natürlich auf Daten- und Labeladressen aufpassen

Die meisten Bascom-Funktionen ergeben im Code dann folgende Teile:

  • Der Aufruf
    • die Vorbereitung, also das Laden von Registern mit den konkreten Argumenten,
    • einen Call auf den eigentliche Funktionscode
    • das Abliefern des Ergebnisses
  • Die Funktion selbst

ADC

Config ADC

CONFIG Adc = Single , Prescaler = Auto
	LDI	r24,0x06
	OUT	ADCSR,r24

Start ADC

START ADC
	SBI	ADCSR,ADEN


Getadc()

  • Aufruf
DIM X AS WORD                  ' laut "prog.RPT"  an der Adresse 0x0063
X = GETADC(0)

ergibt folgenden Code:

	LDI	r24,0x00       ' ADC-Kanal-Nummer nach Register 24
	OUT	ADMUX,r24      ' in den ADC-Multiplexer

	CALL	L_0x00F6       ' Aufruf der getadc-funktion

	LDI	XL,0x63        ' laden der Ergebnisadresse  (DIM X AS WORD)
	LDI	XH,0x00
	ST	X+,r24         ' Speichern ergebnis   (R24:r25) in "X"
	ST	X,r25
  • Funktion
L_0x00F6:
	SBI	ADCSR,ADSC   ' Starten der 1. Konversion
L_0x00F8:
	SBIC	ADCSR,ADSC   ' Fertig ? 
	RJMP	L_0x00F8     ' nein, Loop1
	SBI	ADCSR,ADSC   ' Starten der 2. Konversion
L_0x00FE:
	SBIC	ADCSR,ADSC   ' Fertig ? 
	RJMP	L_0x00FE     ' nein, Loop2
	IN	r24,ADCL     ' Ergebnis auslesen r24:r25
	IN	r25,ADCH
	RET                  ' fertig

BITWAIT

Bitvariable

  • Aufruf
Dim A As Bit
BITWAIT A , Set                                             'wait until bit a is set
  • Code
L_0x007A:
	LDS	r24,0x0060
	SBRS	r24,7
	RJMP	L_0x007A

IO-Register

  • Aufruf
BITWAIT Pinb.7 , Reset                                     'wait until bit 7 of Port B is 0.
  • Code
L_0x008C:
	SBIC	PINB,PB7
	RJMP	L_0x008C

BITVARIABLE

Vielleicht zu Erläuterung: Die erste angegebene Bitvariable ist nicht BITCONTAINER.0, sondern BITCONTAINER.7, dann weiter zu BITCONTAINER.6 usw.

Beispiele

Eine Bitvariable setzen (oder Löschen), ist harmlos, das geht nicht anders.

'----------------------------------
  Bit0 = 1
'----------------------------------------
	LDS	r24,0x0060
	ORI	r24,0x80
	STS	0x0060,r24

Eine Bitvariable in eine andere übertragen zeigt sich schon lebhafter

  Bit1 = Bit0
	LDI	XL,0x60     ' low (Bitcontainer)
	LDI	XH,0x00     ' high (Bitcontainer)
	LD	r24,X       ' into R24
	BST	r24,7	    ' BIT0 --> T-Bit
	LDI	XL,0x60     ' low (Bitcontainer)
	LDI	XH,0x00     ' high (Bitcontainer)
	LD	r24,X       ' into R24
	BLD	r24,6	    ' T-BIT --> BIT1
	ST	X,r24       ' store

Da wär für einen Optimizer schon was zu machen, aber das hab' ich nicht probiert.

Ein klassische BIT-Variablen Abfrage

  If Bit0 = 1 Then
     Bit1 = 1
  Else
      Bit1 = 0
  End If
'----------------------------------------

Das zerfällt in zwei Stufen: Das gefragte Bit wird ins T-Bit transferiert, abhängig davon ist am Ende dann R16 auf 0 oder 1

	CLR	r16
	LDI	XL,0x60
	LDI	XH,0x00
	LD	r24,X
	BST	r24,7
	BRTC	L_0x00B0
	LDI	r16,0x01
L_0x00B0:

Der Wert "1" wird ins R20 geladen, damit wird nun R16 verglichen. Es geht dann zu "IF-Then" oder "IF-Else"

	LDI	r20,0x01     
	CP	r16,r20
	BREQ	L_0x00BA      ' R16 = 1 --> THEN
	JMP	L_0x00C8      ' R16 = 0 --> ELSE

' ------ THEN 
L_0x00BA:
	LDS	r24,0x0060    ' Bitcontainer -> R24
	ORI	r24,0x40      ' setzen BIT1   ( .6)   
	STS	0x0060,r24    ' store container
	JMP	L_0x00D2     ' --> END IF

' ------ ELSE
L_0x00C8:
	LDS	r24,0x0060    ' Bitcontainer -> R24
	ANDI	r24,0xBF      ' löschen BIT1   ( .6)   
	STS	0x0060,r24    ' store container

' ------ END IF
L_0x00D2:

PORT-BIT Abfrage

Ein Beispiel

  • Bascom
   If Pinb.3 = 1 Then
      Portb.4 = 1
   Else
      Portb.4 = 0
   End If

Bascom macht das erstaunlich kompliziert.

Erstmal wird R16 auf NULL gesetzt

	CLR	r16		' R16 = 0

Dann PINB --> R24

	CLR	XH
	LDI	XL,0x36		' PINB addr
	LD	r24,X           ' PINB -> R24

Nun PINB.3 --> T-Bit

	BST	r24,3		'

Ist das T-Bit = 1, wird R16 auf 1 korrigiert

	BRTC	L_0x012E	' 
	LDI	r16,0x01	' 
L_0x012E:

Abhängig von R16 wird nun das Out-Port gesetzt oder gelöscht.

	LDI	r20,0x01	' R20 = &H01
	CP	r16,r20		' R16 <> R20
	BREQ	L_0x0138	' equal-> Setzen
	JMP	L_0x013E	' not equal-> löschen
L_0x0138:
	SBI	PORTB,PB4	' PORTB.4 = 1  (PORTB.PB4)
	JMP	L_0x0140
L_0x013E:
	CBI	PORTB,PB4	' PORTB.4 = 0  
L_0x0140:

BIT Set & Clear

Um einzelne Bits in einer Variablen zu setzen oder zu löschen, kann die Bitnummer auch als Variable angegeben werden. Ein Beispiel

  • Bascom
Dim A As Byte
Dim B As Byte
Dim V1 As Byte
Dim V2 As Byte

      V1 = 3
      V2 = 6
      B.v2 = 1
      A.v1 = B.v2
  • V1 = 3
        LDI	r24,0x03           ' = 3
	STS	0x0062,r24         'Adresse von "V1"
  • V2 = 6
	LDI	r24,0x06           ' = 6
	STS	0x0063,r24         'Adresse von "V2"
  • B.v2 = 1
	SET                    ' set T-Bit

	LDI	XL,0x63        ' adresse "V2"
	LDI	XH,0x00
	LD	r24,X	       ' der Wert von V2 (=6)

	LDI	XL,0x61	       ' Adresse der Variablen "B"
	LDI	XH,0x00
	CALL	L_0x010A       ' Adresse ausrichten 

	CALL	L_0x00F6       ' Bit-Nummer in R24  auf Maske R24 und ~Maske

	LD	r0,X           ' Aktuelles Byte aus der Variablen
	BRTS	L_0x00A2       ' T-BIT  ( setzen oder löschen )
	AND	r0,r25         ' löschen
	RJMP	L_0x00A4     
L_0x00A2:
	OR	r0,r24         ' setzen
L_0x00A4:
	ST	X,r0           ' Byte speichern 


  • A.v1 = B.v2
'------------------------------------------------
' Das Bit B.V2  ins T-Bit übertragen
'------------------------------------------------
	LDI	XL,0x63
	LDI	XH,0x00
	LD	r24,X
	LDI	XL,0x61
	LDI	XH,0x00
	CALL	L_0x010A       ' Adresse von "B" aurichten
	CALL	L_0x00F6       ' BitNummer zu Maske 
	LD	r0,X           ' aktuelles Byte
	AND	r0,r24         ' T-Bit übernimmt das Bit
	SET
	BRNE	L_0x00C2
	CLT
L_0x00C2:

'------------------------------------------------
' Das T-Bit  ins Bit A.V1  übertragen
'------------------------------------------------
	LDI	XL,0x62
	LDI	XH,0x00
	LD	r24,X
	LDI	XL,0x60
	LDI	XH,0x00
	CALL	L_0x010A       ' Adresse von "A" aurichten
	CALL	L_0x00F6       ' BitNummer zu Maske 
	LD	r0,X           ' aktuelles Byte
	BRTS	L_0x00DC       ' T-Bit ?
	AND	r0,r25         ' löschen
	RJMP	L_0x00DE
L_0x00DC:
	OR	r0,r24         ' setzen
L_0x00DE:
	ST	X,r0           'Speichern "A"


  • Bitnummer zu BitMaske und invertierter Maske
L_0x00F6:
	LDI	r25,0x01
	AND	r24,r24
	BREQ	L_0x0104
	CLC
L_0x00FE:
	ROL	r25
	DEC	r24
	BRNE	L_0x00FE
L_0x0104:
	MOV	r24,r25
	COM	r25
	RET
  • Variablen X = Adresse + (R24 / 8) und R24 = R24 Modulo 8
L_0x010A:
	CPI	r24,0x08
	BRLO	L_0x0114
	ADIW	XL,0x0001
	SUBI	r24,0x08
	RJMP	L_0x010A
L_0x0114:
	RET

PULSEIN

DIM Result AS WORD
PULSEIN Result , Pind , 2 , 1
  • Aufruf
	LDI	ZL,0x30         ' Adresse von SFR PIND
	LDI	r24,0x02	' PinNr 2
	LDI	r16,0xFF        ' State 1

	CALL	PULSEIN

	LDI	XL,0x60         ' laden der Ergebnisadresse  
	LDI	XH,0x00
	ST	X+,r24		' store result
	ST	X,r25

Hier kommen mehrere Funktionen zum Einsatz, die auch ggf. für andere Zwecke aufgerufen werden.

  • Idle Loop
'-----------------------------------------------------
' der Wert in ZL:ZH wir auf Null heruntergezählt, dann geht's zurück
'-----------------------------------------------------
L_0x009C:
	SBIW	ZL,0x0001
	BRNE	L_0x009C
	RET
  • Set_ErrBit

Zur Erinnerung: Das Err-Bit ist das, was man mit "IF ERR = 1 THEN.." abfragt

	SET                     ' Setzen T-Bit
	BLD	r6,2            ' nach R6.2
	RET
  • Clear_ErrBit
	CLT                     ' Löschen T-Bit
	BLD	r6,2            ' nach R6.2
	RET
  • MakeMask
'-----------------------------------------------------
' Eine Bit-Nummer in r24 (0-7) wird in eine Bit-Maske umgewandelt (1,2,4,...128)
' nach r25 kommt dann die invertierte Maske
'-----------------------------------------------------
	LDI	r25,0x01             ' Maske = &B0000001
	AND	r24,r24              ' BitNummer = 0 ?
	BREQ	L_0x00BC             ' Ja, fertig
	CLC                          ' löschen Carry
L_0x00B6:
	ROL	r25                  ' Maske eins nach links
	DEC	r24                  ' Bitnummer runterzählen 
	BRNE	L_0x00B6             ' nochmal
L_0x00BC:
	MOV	r24,r25              ' Maske kopieren nach r24
	COM	r25                  ' und r25 invertieren
	RET                          ' that's it.
  • Pulsein, die eigentliche Funktion
PULSEIN:
	CALL	Clear_ErrBit
	CLR	ZH
	CLR	XL		// clear Timout Lo
	CLR	XH		// clear Timout Hi
	CALL	MakeMask        // R24 Mask, R25 neg Mask
	AND	r16,r24
	LDD	r0,Z + 1	// DDRD
	AND	r0,r25		// Make Input
	STD	Z + 1,r0	// DDRD
'---------------------------------------------------------------------------
' Warten, bis der Pin eine Zustand einnimmt, der anders ist als der, den man 
' messen will. Sprich, will ich die Dauer eines HIGH Zustandes messen, wartet 
' die Funktion erstmal, bis ein LOW anliegt. Eigentlich logisch. 
' Dabei wir das Registerpaar XL:XH von 0-65535 hochgezählt. Bei Überlauf wird 
' Bascom ERR gesetzt und abgebrochen. 
' Dieses Zählen ist aber nicht getimed, d.h. mit einem schnellen Quartz geht
' es auch schneller, bzw. das Timeout ist kürzer. 
'---------------------------------------------------------------------------
L_0x00D8:	// -------------	Loop
	LDD	r0,Z + 0	// PIND
	AND	r0,r24		// PIND & Mask 
	EOR	r0,r16		// (PIND & Mask) ^ State 
	BRNE	L_0x00E6	// Ok  PIN != State
	ADIW	XL,0x0001	// Timeout counter++
	BREQ	L_0x0118	// elapsed->ERR-Exit
	RJMP	L_0x00D8	// cont'd Loop
'---------------------------------------------------------------------------
' Und jetzt wird gewartet, bis der Pin den zu messenden Zustand einnimmt. Im Idealfall
' ist das genau die Flanke, garantieren kann man das aber nicht. 
' Auch hier der gleiche "Time"out zähler wie oben. 
'---------------------------------------------------------------------------
L_0x00E6:
	CLR	XL		// clear Timout Lo
	CLR	XH		// clear Timout Hi
L_0x00EA:	// -------------	Loop
	LDD	r0,Z + 0	// PIND
	AND	r0,r24		// PIND & Mask 
	EOR	r0,r16		// (PIND & Mask) ^ State 
	BREQ	L_0x00F8	// Ok  PIN == State
	ADIW	XL,0x0001	// Timeout counter++
	BREQ	L_0x0118	// elapsed->ERR-Exit
	RJMP	L_0x00EA	// cont'd Loop
'---------------------------------------------------------------------------
' jetzt wird's ernst. Jetzt wird in 10µS Intervallen der Impuls HIGH (od.LOW) 
' gemessen. Das Ergebnis also 0 - 65535 * 10 µS. Das reicht etwa für eine 1/2 
' Sekunde. Gibt's eine Überlauf, wird ERR gesetzt. 
'---------------------------------------------------------------------------
L_0x00F8:
	CLR	XL		// clear Timout Lo
	CLR	XH		// clear Timout Hi
L_0x00FC:
	PUSH	ZL		// Save
	PUSH	ZH
	LDI	ZL,0x20		// Dieser Wert wurde aus $CRYSTAL berechnet		
	LDI	ZH,0x00
	CALL	L_0x009C	// 10 µS Idle
	POP	ZH		// Restore
	POP	ZL
	LDD	r0,Z + 0	// PIND
	AND	r0,r24		// Maskieren des PIND.x 
	EOR	r0,r16		// (PIND & Mask) ^ State 
	BRNE	L_0x011C	// OK, die Funktion ist fertig 
	ADIW	XL,0x0001	// PulseCounter++
	BRNE	L_0x00FC	// und weiter
L_0x0118:
	CALL	Set_ErrBit      // Irgendein Überlauf ist eingetreten
L_0x011C:
	MOV	r24,XL		// Ergebnis --> R24:r25
	MOV	r25,XH
	RET			// Zurück 

(Quadratur-) ENCODER

"Result" sollte man in Ruhe lassen oder nur lesen, es wird immer gebraucht, um den aktuellen Zustand der A / B Pins für den nächsten Check zu speichern.

Und noch ein Tip: Beim ersten Aufruf kommt in den meisten Fällen was Falsches raus, da ja noch kein gültiger "alter" Wert existiert. Also alle Encoder ersteinmal nur abfragen, aber nix zählen. Ab dann stimmt die Sache erst wirklich.

  • Aufruf
 DIM Result AS BYTE
 Result = ENCODER(pind.1 , Pind.3 , Leftlabel , Rightlabel , 0)
  ...
  ...
Leftlabel:
   Return
Rightlabel:
   Return
  • Code
	LDI	XL,0x60			' Ergebnis (s.o)
	LDI	XH,0x00
	CLT				' clear t-bit  (nicht warten)
L_0x008C:
	LD	r20,X			' Lesen Result (vorheriger Pin-Wert)
	IN	r16,PIND                ' lesen PIN (d)
	CLR	r24			' Temp-Register clear
	SBRC	r16,1			' pind.1 (A) ("skip-if-bit-clear")
	ORI	r24,0x01		' R24 OR 1
	SBRC	r16,3			' pind.3 (B)
	ORI	r24,0x02		' R24 OR 2
	CP	r24,r20			' Vergleich mit altem Wert 
	BRNE	L_0x00A2		' Da gab's eine Änderung
	BRTS	L_0x008C		' t-bit ? Wenn gesetzt, dann warten wir 	
	RJMP	L_0x00C2		' no diff, no wait -> no job --> exit
'------------------------------------------------------------------------------
' das Register r24 hat nun den Level A/B auf den Bits 0 u. 1
' &B000000AB  
'------------------------------------------------------------------------------
L_0x00A2:
	ST	X,r24			' speichern für's nächste Mal 
	SWAP	r20			' den alten Werte "swappen"
'------------------------------------------------------------------------------
' das Register r20 hat nun den (alten) Level A/B auf den Bits 4 u. 5
' &B00ab0000  
'------------------------------------------------------------------------------
	ADD	r24,r20			' OLD + NEW
'------------------------------------------------------------------------------
' jetzt stehen in r24 alter UND neuer Wert
' &B00ab00AB
'------------------------------------------------------------------------------
' Aus Alt & Neu läßt sich eindeutig aus allen 4 Phasen die Drehrichtung ermitteln 
' Bascom checkt nur explizit auf die eine Richtung. Denn wenn's die nicht ist, muß es 
' ja wohl die andere sein. 
'------------------------------------------------------------------------------
' Achtung: daraus folgt: wenn die Impulse zu schnell kommen, stimmen 
' die Ergebnisse u.U. NICHT ! 
' Was vielleicht schlimmer ist: Man merkt es auch nicht
'------------------------------------------------------------------------------
	CPI	r24,0x02
	BREQ	Leftlabel		' left
	CPI	r24,0x10
	BREQ	Leftlabel		' left
	CPI	r24,0x23
	BREQ	Leftlabel		' left
	CPI	r24,0x31
	BREQ	L_0x00BE		' left
	CALL	Rightlabel		' call Right
	RJMP	L_0x00C2		' xit
L_0x00BE:
	CALL	Leftlabel		' call Left
L_0x00C2:

Anmerkung: hier ist kein Return, da der Code direkt eingefügt wird

Leftlabel:
	RETURN
Rightlabel:
	RETURN

Die Links-Rechts Routinen sind hier sparsam gehalten. Normalerweise wird man hier wohl Schritte zählen.

Dividieren 32Bit (LONG)

Das folgende Beispiel ist eigentlich eine Bascom-Source mit inline Assembler. Es zeigt, wie Bascom zwei signed LONG dividiert, und durch den Assembler kann man sich das auch im Simulator bitweise anschauen, wie das läuft.

Anmerkung: Nach der Division befindet sich der Divisionsrest in r16:r19. Wenn den jemand braucht, könnte er ihn nach einer long-Division dort abholen.

  • Aufruf
$regfile = "m32def.dat"                                     ' specify the used micro
$crystal = 8000000

Dim Vala As Long
Dim Valb As Long
Dim Valc As Long

   Vala = 14
   Valb = 1
' das Bascom Äquivalent wäre:
'      Valc = Vala / Valb
$asm
   Loadadr Vala , X                    ' Laden r16:r19 mit Vala
   LD r16,X+
   LD r17,X+
   LD r18,X+
   LD r19,X+
   Loadadr Valb , X                    ' Laden r20:r23 mit Valb
   LD r20,X+
   LD r21,X+
   LD r22,X+
   LD r23,X+
$end Asm

   Gosub L_0x0112                      ' Dividieren  r16:r19 / r20:r23 --> r20:r23

$asm
   Loadadr Valc , X                    ' speichern Ergebnis in Valc
   ST X+,r20
   ST X+,r21
   ST X+,r22
   ST X+,r23
$end Asm

End
  • Funktions-Code
'-----------------------------------------
'
'-----------------------------------------
L_0x0112:
$asm
   RCALL L_0x0174        'Operanden Vorzeichen prüfung und ggf. umdrehen auf positiv
                         'register r0.0 zeigt, ob das Ergebnis negativ ist
   RCALL L_0x0120        'Prüfen Divisor auf NULL. Wenn nicht, dann dividieren
   BLD r0,1              't-bit -> r0.1 (Error)
   RJMP L_0x010C         'finish

L_0x010C:
   SBRC r0,0             'Ergebnis negativ ?
   RCALL L_0x011A	 'ja, Ergebnis negativ machen
   RET                   'so oder so, Funktionsende

L_0x011A:
   RCALL L_0x018A        'Ergebnis auf negativ (r20:r23)
   RCALL L_0x019C        'Rest auf negativ (r16:r19) 
   RET


'-----------------------------------------
' Prüfen divisor auf NULL 
'-----------------------------------------
L_0x0120:
   MOV r24,r20                 'Kopieren r20:r23 -> r24:r27
   MOV r25,r21
   MOV r26,r22
   MOV r27,r23
   CLT			       ' clear T-bit
   !OR r20,r21                 ' prüfen auf NULL 
   !OR r20,r22
   !OR r20,r23
   BRNE L_0x0136               ' <> NULL --> dividieren
   !SET                        ' ==NULL set t-Bit
   RET
'-----------------------------------------
' dividieren r16:r19 / r20:r23  --> r20:r23
'-----------------------------------------
L_0x0136:
   MOV r20,r16                 'r16:r19->r20:r23
   MOV r21,r17
   MOV r22,r18
   MOV r23,r19
   CLR r16                     'clear r16:r19
   CLR r17
   CLR r18
   CLR r19

   LDI ZH,32                   'Zähler  (32 Bit)
'-----------------------------------------
' divisions-Schleife
'-----------------------------------------
L_0x0148:
   LSL r20                     'shift left r20:r23
   ROL r21                   
   ROL r22
   ROL r23                     '(das oberste Bit wandert nach r16:r19)

   ROL r16                     'shift left r16:r19
   ROL r17
   ROL r18
   ROL r19

   !SUB r16,r24                'subtrahieren 
   SBC r17,r25
   SBC r18,r26
   SBC r19,r27

   ORI r20,1                   'Ergebnis auf 1
   BRCC L_0x016E               'kein Überlauf, bleibt so

   ADD r16,r24                 'Überlauf, wieder addieren
   ADC r17,r25
   ADC r18,r26
   ADC r19,r27
   ANDI r20,254               'und den Ergebnis 1-er wieder löschen
L_0x016E:
   DEC ZH                     'Zähler--
   BRNE L_0x0148              '23-Bit Schleife
   RET                        'fertig

'-----------------------------------------
' Prüfen der Operanden 
'-----------------------------------------
L_0x0174:
   CLR r0                     'Kontroll-Register säubern
   CLT                        't-bit löschen
   SBRS r23,7                 ' r20:r23 Negativ ?
   RJMP L_0x0180              'nö, ist positiv
   RCALL L_0x018A             'ja, auf positiv umdrehen 
   !SET                       'und t-bit setzen
L_0x0180:
   BLD r0,0                   't-bit ins Kontroll-register r0.0
   RCALL L_0x0196	      'Prüfen r16:r19
   BLD r1,0		      't-bit -> r1.0
   EOR r0,r1		      'Exklusiv Or der beiden +- Prüfungen
                              'Vorzeichen verschieden ---> Ergebnis muss negativ sein
   RET                        'e bien
'-----------------------------------------
' r20:r23 auf positiv
'-----------------------------------------
L_0x018A:
   RCALL L_0x01B0             'invertieren
   SUBI r20,0xFF              ' +1
   SBCI r21,0xFF
   SBCI r22,0xFF
   SBCI r23,0xFF
   RET
'-----------------------------------------
' Prüfen  r16:r19
'-----------------------------------------
L_0x0196:
   CLT                        'clear t-bit
   SBRS r19,7                 'sign bit set ?
   RET                        'no
'-----------------------------------------
' r16:r19 auf positiv
'-----------------------------------------
L_0x019C:
   COM r16                   'invertieren
   COM r17
   COM r18
   COM r19
   SUBI r16,0xFF             '+1
   SBCI r17,0xFF
   SBCI r18,0xFF
   SBCI r19,0xFF
   !SET                     't-bit setzen
   RET
'-----------------------------------------
'  invertieren r20:r23
'-----------------------------------------
L_0x01B0:
   COM r20
   COM r21
   COM r22
   COM r23
   RET

$end Asm

Return

Multiplizieren 32Bit (LONG)

Hier ist ein Unterschied, ob der µC über den MUL-Befehl verfügt.

Mit "MUL" (Atmega32)

	MOVW	r24,r16         'r16:r19 -> r24:r27
	MOVW	r26,r18

	MUL	r20,r24         'r20 * r24
	MOVW	r16,r0          '-> r16:r17

	MUL	r20,r26		'r20 * r26
	MOVW	r18,r0          '-> r18:r19

	MUL	r20,r27		'r20 * r27
	MOV	r2,r0		'--> r2

	MUL	r20,r25		'r20 * r25
	ADD	r17,r0		'r17:r18:r19 + r0:r1:r2
	ADC	r18,r1
	ADC	r19,r2

	MUL	r21,r26		'r21 * r26
	MOV	r2,r0
	MUL	r21,r24		'r21 * r26
	ADD	r17,r0		'r17:r18:r19 + r0:r1:r2
	ADC	r18,r1
	ADC	r19,r2

	MUL	r21,r25		'r21 * r26
	ADD	r18,r0		'r18:r19 + r0:r1
	ADC	r19,r1

	MUL	r22,r24		'r22 * r26
	ADD	r18,r0		'r18:r19 + r0:r1
	ADC	r19,r1

	MUL	r22,r25		'r22 * r26
	ADD	r19,r0		'r19 + r0

	MUL	r23,r24		'r23 * r26
	ADD	r19,r0		'r19 + r0

	RET

Ohne "MUL" (AT90S2313)

Hier wird "zu Fuß" multipliziert. Das Vorbereiten der Operanden ist wie beim Dividieren (Prüfung auf NULL entfällt natürlich), auch die Korrektur der Ergebnisses ist genauso (Ergebnis ist diesmal aber in R16:r19).

Ich beschränke mich daher auf das Listing der eigentlichen Multiplikation der beiden 32-Bit Zahlen R16:r19 * r20:r23 ---> r16:r19

'------------------------------------------
'
'------------------------------------------
	MOV	r24,r20      'r20:r23 ---> r24:r27
	MOV	r25,r21
	MOV	r26,r22
	MOV	r27,r23
	CLR	r20          'Löschen r20:r23
	CLR	r21
	CLR	r22
	CLR	r23
	LDI	ZH,0x21      'Zähler 31 (Überlauf nach 32 Bit wird nicht berücksichtigt)
	CLC
	RJMP	L_0x00B6     'Einstieg in die Schleife
'------------------------------------------
'  Schleife
'------------------------------------------
L_0x00A4:
	BRCC	L_0x00AE     
	ADD	r20,r24      ' Nur bei Carry wird addiert
	ADC	r21,r25
	ADC	r22,r26
	ADC	r23,r27
L_0x00AE:
	LSR	r23          'shift right r20:r23
	ROR	r22
	ROR	r21
	ROR	r20          ' Carry -> r16:r19

L_0x00B6:                    'Einsprungs-Stelle
	ROR	r19          'shift right r16:r19
	ROR	r18
	ROR	r17
	ROR	r16
	DEC	ZH           'Zähler
	BRNE	L_0x00A4     'Schleife <> 0
	RET                  'fertig

SERIN

SERIN (und SEROUT) stellen die allgemeinste Form der Software UART dar. Es kann zur Run-Time von einem Aufruf zum nächsten so ziemlich alles verändert werden: PIN, Polarität, Baudrate, Stopp-Bits, Databits (7 od.8 ) und Parity. Anders als bei der normalen SW-UART kann der gleiche Pin zum Senden und Empfangen werden, was für einige half-duplex Kommunikationsformen sehr praktisch ist. Es kann aber nur entweder empfangen oder gesendet werden, eine Bus-Arbitrierung ist also nicht möglich.

Diese Variationsmöglichkeiten haben ihren Preis. Insbesonders die Berechnung der Delay-Counter für die Baudrate, die bei jedem Aufruf neu geschieht, tut weh.

Aber wir wollen hier auch keine Urteile abgeben, sondern nur "sine ira et studio" die Dinge zeigen, wie sie sind. Zum daraus lernen (und ev. dann besser machen) reicht es immer.

A propos: Beim Empfangen wird "parity" zwar akzeptiert, geprüft wird das aber nicht

  • Aufruf
$crystal = 8000000
Dim Mystring As String * 10
Dim Mybaud As Long

  Mybaud = 9600

  Serin Mystring , 0 , D , 0 , Mybaud , 0 , 8 , 1

Zur Bedeutung der Parameter bitte Bascom-Help verwenden. Nur soviel: es wird unterschieden, ob die Empfangs-variable ein String ist oder nicht. Wenn ja, läuft die Routine, bis ein <CR> empfangen wird, ansonsten muß eine Länge angegeben werden.


Zur Berechnung der Schleifenzähler werden die internen 32-Bit Funktionen verwendet, die hatten wir schon, also laß ich sie hier weg.

  • Code

Vorbereiten der Parameter

	LDI	XL,0x6B       'Adresse von "MyBaud"
	LDI	XH,0x00
	LD	r16,X+        ' Baudrate --> r16:r19
	LD	r17,X+
	LD	r18,X+
	LD	r19,X

	LDI	XL,0x6F       ' next free SRAM ("___SER_BAUD") (LONG)
	LDI	XH,0x00
	ST	X+,r16        ' speichern der Baudrate
	ST	X+,r17
	ST	X+,r18
	ST	X,r19

	CALL	L_0x0374	'calc counter nach "___SER_BAUD"
'---------------------------------------------------------------------------
'Bascom berechnet:  Counter =  (( $CRYSTAL / Baudrate ) + 42 ) / 8 
'Das ist ein Schleifenzähler für die Dauer eines halben Bits
'---------------------------------------------------------------------------
	LDI	r24,0x01	'1 stopp-bit
	ST	-Y,r24
	LDI	r24,0x00	'no parity
	ST	-Y,r24
	LDI	r24,0x08	'8 Bit
	ST	-Y,r24
	LDI	XL,0x60		' Datenempfangs-adresse ("MyString")
	LDI	XH,0x00
	CLR	r24             ' Count =null, weil bei string auf <CR> gewartet wird
	ST	-Y,r24		
	LDI	ZL,0x31		' Adresse von  DDRD für PinD 
	LDI	r24,0x00        ' Pin # 0  
	CLT			' Polarität 
	CALL	L_0x02BE	' CALL der eigentlichen "SERIN" Funktion
  • Die Funktion
'-----------------------------------------
'SERIN
'-----------------------------------------
L_0x02BE:
	RCALL	MakeMask	' Pin-# nach BitMask und !BitMask (siehe PULSEIN)
	CLR	ZH              '

	LDD	r0,Z + 0	' adresse von DDRD
	AND	r0,r25		' Pin wird Input
	STD	Z + 0,r0	

	INC	ZL		' jetzt zeigt Z auf PORTD

	LDD	r0,Z + 0        ' Clear PORTD.x
	AND	r0,r25
	STD	Z + 0,r0

	SBIW	ZL,0x0002       ' jetzt zeigt Z auf PIND

	MOV	r16,r24		' Mask
	MOV	r22,r25		' !Mask

	LD	r20,Y+          ' Byte count  
	MOV	r19,r20
'-------------------------------------------------------------------------
' Ausrechnen der effektive Framesize  (Data-Bits, Parity, Stopp-Bits)
'-------------------------------------------------------------------------
L_0x02DA:
	LDD	r17,Y + 0	' Data-Bits (7/8)
	CPI	r17,0x07	' 7 Bit ?
	BRNE	L_0x02F2	' nein, es sind 8
'---------------------------------------------------
' 7 Data-Bits 
'---------------------------------------------------
	LDD	r17,Y + 1	' parity 
	CPI	r17,0x00        ' Y/N
	BRNE	L_0x02EC        ' Y
	LDD	r18,Y + 2       ' stopp-bits
	SUBI	r18,0xF9	' +7    (0xF9 == -7 )
	RJMP	L_0x0302        
L_0x02EC:
	LDD	r18,Y + 2	' stopp-bits
	SUBI	r18,0xF8	' +8    (0xF8 == -8 )
	RJMP	L_0x0302
'---------------------------------------------------
' 8 Data-Bits
'---------------------------------------------------
L_0x02F2:
	LDD	r17,Y + 1	' parity
	CPI	r17,0x00        ' Y/N
	BRNE	L_0x02FE	' Y 
	LDD	r18,Y + 2	' stopp-bits	
	SUBI	r18,0xF8	' +8    (0xF8 == -8 )
	RJMP	L_0x0302
L_0x02FE:
	LDD	r18,Y + 2	' stopp-bits
	SUBI	r18,0xF7	' +9    (0xF7 == -9 )	

L_0x0302:
	MOV	r21,r18         ' Framesize
'----------------------------------------
' Der eigentlich BYTE-Read-Loop
'----------------------------------------
' R18 /R21 frame bits  
' X data pointer  
' Z PIN(d)  
' T-Bit revert polarity 
' Y Parameter  
' r20 Laufender Byte-Zähler
' r19 gewünschte Anzahl Bytes (bei einem String ist das NULL)
'----------------------------------------


'----------------------------------------
' Die fallende(od. steigende) Flanke des Start-Bits finden
'----------------------------------------
L_0x0304:
	LDD	r0,Z + 0	' Rx Pin
	AND	r0,r16		' mask
	BRTS	L_0x030E	' T-Bit ?
	BREQ	L_0x0312	' Low--> start
	RJMP	L_0x0304	' wait loop

' reverse polarity
L_0x030E:   
	BRNE	L_0x0312	' high--> start
	RJMP	L_0x0304	' wait loop

'----------------------------------------
'StartBit Flanke gefunden, bis zur ~Mitte dauert es noch 1/2 Bit
'----------------------------------------
L_0x0312:
	RCALL	L_0x035E	' Warten 1/2 Bit
'----------------------------------------
' Data Bit Loop
'----------------------------------------
L_0x0314:
	RCALL	L_0x035E	' Warten 1/2 Bit			
	RCALL	L_0x035E	' Warten 1/2 Bit			

	CLC			' clear carry		
	LDD	r0,Z + 0	' Lesen PIN 
	AND	r0,r16		' Maskieren Bit
	BREQ	L_0x0322	' Bit ist Low
	SEC			' Bit ist High --> set carry
L_0x0322:
	DEC	r18		' Frame Bit-Zähler 
	BREQ	L_0x032C	' fertig
	ROR	r24		' das Carry reinschieben nach r24:r25
	ROR	r25		
	RJMP	L_0x0314	' und weiter

L_0x032C:
	CPI	r21,0x0A	' Framesize  <> 10 	?
	BRLO	L_0x033A	' lower
	SUBI	r21,0x09	' >= 10 --> sub 9
L_0x0332:
	ROL	r25		' Ausrichten Data Byte 
	ROL	r24
	DEC	r21             '
	BRNE	L_0x0332	' Zählschleife
L_0x033A:
	BRTC	L_0x033E        ' Polarität ?
	COM	r24		' Umdrehen data Byte
L_0x033E:
	LDD	r17,Y + 0       ' Data-Bits
	CPI	r17,0x07
	BRNE	L_0x0346        ' = 8
	ANDI	r24,0x7F	' = 7 , also löschen 2^^7
L_0x0346:
	ST	X+,r24		' speicher DataByte
	CPI	r19,0x00	' soll es ein string werden ?
	BRNE	L_0x0358	' nein
	CPI	r24,0x0D	' ja, ist das ein <CR>
	BRNE	L_0x02DA	' nö, weiter in der Byte-Schleife
	CLR	r24		' Ja, in der Empfangs string wird aber statt dessen 
	ST	X,r24		' 0x00 geschrieben, damit es ein Null-terminierter String wird
L_0x0354:
	ADIW	YL,0x0003	' restore Y auf den Einstiegswert
	RET			' fertig
L_0x0358:
	DEC	r20		' kein String, müssen wir also zählen 
	BREQ	L_0x0354	' geschafft.
	RJMP	L_0x02DA	' weiter geht's

'-------------------------------------------
' 1/2 Bit Delay
'-------------------------------------------
L_0x035E:
	PUSH	r24		
	PUSH	r25		
	LDS	r24,0x006F	' das LSB von "___SER_BAUD" (s.o.)
	LDS	r25,0x0070	' das MSB
L_0x036A:
	SBIW	r24,0x0001	
	BRNE	L_0x036A	
	POP	r25		
	POP	r24		
	RET			

SERVO

Damit können bis zu 16 Servos an beliebigen Port-Pins versorgt werden (alternative Servoansteuerungsmöglichkeit). Bascom nimmt dazu einen Timer in Beschlag (defaultmäßig Timer0). Für zwei Servos (Beispiel) wird konfiguriert:

Config Servos = 2 , Servo1 = Portb.0 , Servo2 = Portb.1 , Reload = 10

"Reload" bestimmt die Auflösung, die Angabe "10" für 10µS wird empfohlen.

Bascom legt einige Felder an:

DIM Counter AS WORD    '(verdeckt und nicht zugänglich, der Name ist nur zur Veranschaulichung)

und

DIM SERVO(2) AS BYTE   ' für zwei Servos, sonst eben mehr oder weniger

In dies Byte schreibt man die gewünschte Servo-Impulsdauer (in 10µS Schritten). Und zwar nur dann, wenn sich was ändert, denn das refreshen ( alle 20mS) übernimmt Bascom völlig selbstätig.

Die Port-Pins muß man übrigens selbst auf "Output" setzen

Config Pinb.0 = Output
Config Pinb.1 = Output
  • Bascom definiert eine ISR-Routine und setzt den Timer auf (TIMER0)
	IN	r24,TCCR0
	ORI	r24,0x01	'no prescale (/1)
	OUT	TCCR0,r24

	IN	r24,TIMSK
	ORI	r24,0x01	'enable
	OUT	TIMSK,r24

	LDI	r24,0xB1	'preload = 177
	OUT	TCNT0,r24

Diese Werte berechnet Bascom aus der "$Crystal=" Angabe. Es ergibt einen Interrupt jede 10µS (s.o).

  • Das übrige Servo-Handling spielt sich ausschließlich in der ISR-Routine ab
'------------------------------------------------
'		
'------------------------------------------------
OVF0ADDR_ISR:
	PUSH	r16
	PUSH	r24
	IN	r24,SREG
	PUSH	r24
	PUSH	XL
	PUSH	XH
	PUSH	r25

	LDI	XL,0x60			' counter++
	LDI	XH,0x00
	LD	r24,X+
	LD	r25,X
	ADIW	r24,0x0001
	ST	X,r25
	ST	-X,r24

	CLR	r16
	CPI	r24,0x01		' count == 1 ?
	CPC	r25,r16
	BRNE	L_0x00C2

	SBI	PORTB,PB0		' PORTB.0 = 1
	SBI	PORTB,PB1		' PORTB.1 = 1
'........... eventuell noch andere Servos
	RJMP	L_0x00E8		' exit ISR

L_0x00C2:
	CPI	r25,0x00		' Msb r24:r25 == 0 ?
	BREQ	L_0x00D6		' yes -->

	CPI	r24,0xD0		' r24:r25 == 2000 ?
	LDI	r16,0x07
	CPC	r25,r16
	BRLO	L_0x00E8		' LOW --> exit ISR

	CLR	r16			' reset counter
	ST	X+,r16
	ST	X,r16
	RJMP	L_0x00E8		'exit ISR

L_0x00D6:
	ADIW	XL,0x0002

	LD	r25,X+			'servo(1)
	CP	r24,r25			'counter == ?
	BRLO	L_0x00E0		'not yet
	CBI	PORTB,PB0		'yes, clear Servo 1
L_0x00E0:
	LD	r25,X+			'servo(2)
	CP	r24,r25			'counter == ?
	BRLO	L_0x00E8		'Zum nächsten Servo oder Exit-ISR
	CBI	PORTB,PB1		'clear Servo 2

'........... eventuell noch andere Servos

L_0x00E8:
	LDI	r24,0xB1
	OUT	TCNT0,r24		' reload Timer 
	POP	r25
	POP	XH
	POP	XL
	POP	r24
	OUT	SREG,r24
	POP	r24
	POP	r16
	RETI
  • Erläuterung: Im Kern wird hier bei jedem Interrupt ein 16-Bit Zähler 'raufgezählt.
    • Bei "Zähler = 2000" wird der Zähler wieder auf Null gesetzt und es geht von vorne los. Bei 10µS Interrupts dauert sowas eben ca 20 mS, das ist der Wert, den übliche Servos zum Refresh brauchen.
    • Bei "Zähler = 1" werden alle Servo-Pins auf "1" gesetzt, d.h. die Impulse werden gestartet.
    • Solange "Zähler < 256", wird nach der Reihe der Zählerstand mit den Werten verglichen, die in dem SERVO(n) Array stehen. Ist der Zähler gleich oder größer, wird der jeweilige Impuls abgedreht. Bei einer Angabe "100" dauert das eben ca 1 mS, bei "200" 2 mS. Das ist der übliche Impuls-Bereich für Servos.
    • Solange "Zähler >= 256" wird nur mehr gezählt, bis es Zeit für einen Refresh ist (s.o).

I2C Software

Für AVRs ohne Hardware-TWI bietet Bascom eine Reihe I2c Funktionen an. Die SDA & SCL Pins können beliebig gewählt werden. Hier gibt's nähere Info über I2C

Config I2cdelay = 5                   ' ergibt lt. "Help" ca 200 kHz
Config Scl = Portc.0                  'Ports/Pins fuer I2C-Bus 
Config Sda = Portc.1

   I2cinit        
   I2cstart
   I2cwbyte Out_adr
   I2cwbyte &H55
   I2cstop
  • I2cinit
	CBI	PORTC,PC1   'Output auf LOW  SDA
	CBI	PORTC,PC0   'Output auf LOW  SCL
	CBI	DDRC,DDC1   'Pin auf Input
	CBI	DDRC,DDC0   'Pin auf Input
	RET

Bascom setzt die SDA & SCL Pins auf "LOW". Gesteuert werden die dann nurmehr durch

  • setzen in DDRx auf Output, dadurch wird die Leitung auf NULL gezogen,
  • oder auf Input, dann zieht der Pullup-Widerstand die Leitung hoch.


  • Verzögerungs-Routine für einen 1/2 Bus-Cycle, aus "I2cdelay=" und "$Crystal=" berechnet.
Idle_Loop:
	SBIW	ZL,0x0001     '2    cyc.
	BRNE	Idle_Loop     '1/2  cyc.
	RET                   '4    cyc.
Idle_Call:
	PUSH	ZL            '2    cyc.
	PUSH	ZH            '2    cyc.
	LDI	ZL,0x06       '1    cyc.      I2cdelay=5, $Crystal=8000000
	LDI	ZH,0x00       '1    cyc.
	CALL	Idle_Loop     '4    cyc.
	POP	ZH            '2    cyc.
	POP	ZL            '2    cyc.
	RET                   '4    cyc.

Ganz klar komme ich mit der Schleife ja nicht. Wenn ich das nachrechne, verbraucht die Routine insgesamt 45 Cyclen, + 3 für einen "RCALL" auf die Routine, wären insgesamt 48 Cycles. Bei 8 MHZ braucht der AVR dafür offensichtlich 6 µS. Also, 200 kHz krieg' ich da dann nicht raus.

  • I2cstart
	SBI	DDRC,DDC0    ' SCL runter
	CBI	DDRC,DDC1    ' SDA rauf
	RCALL	Idle_Call    ' etwas warten
	CBI	DDRC,DDC0    ' SCL rauf
	RCALL	Idle_Call    ' wieder warten
	SBI	DDRC,DDC1    ' SCL runter  (Startbedingung)    
Idle_Call:
        (s.o)                ' wieder warten
  • I2cstop
I2CSTOP:
	SBI	DDRC,DDC0    ' SCL runter
	SBI	DDRC,DDC1    ' SDA runter
	RCALL	Idle_Call
	CBI	DDRC,DDC0    ' SCL rauf
L_0x00E4:
	SBIS	PINC,PINC0   ' check "Stretching"
	RJMP	L_0x00E4     ' noch nicht oben, nochmal
	RCALL	Idle_Call    ' warten
	CBI	DDRC,DDC1    ' SDA rauf   (Stoppbedingung)
	RJMP	Idle_Call    ' wieder warten und tschüss
  • I2cwbyte (reg 17 = Data-Byte)
	SEC
	ROL	r17          ' Jetzt ist das MSB im Carry, und ein 1-er ins R17 reingeschoben
	RJMP	L_0x00F6     ' Schleifen-Einstieg
L_0x00F4: ----------------- SCHLEIFE ---------------
	LSL	r17
L_0x00F6:
	BREQ	L_0x0112     ' R17 == NULL ?  --> fertig
	SBI	DDRC,DDC0    ' SCL runter
	BRCC	L_0x0102     ' SDA Null oder EINS ?
	NOP
	CBI	DDRC,DDC1    ' EINS, also SDA rauf
	RJMP	L_0x0106
L_0x0102:
	SBI	DDRC,DDC1    ' NULL, also SDA runter
	RJMP	L_0x0106
L_0x0106:
	RCALL	Idle_Call    ' warten
	CBI	DDRC,DDC0    ' SCL  rauf
L_0x010A:
	SBIS	PINC,PINC0   ' "stretcht" da einer ?
	RJMP	L_0x010A     ' ja
	RCALL	Idle_Call    ' jetzt ist SCL wirklich oben, warten
	RJMP	L_0x00F4     ' das nächste Bit

L_0x0112:
	SBI	DDRC,DDC0    ' SCL runter für das ACK-Bit
	CBI	DDRC,DDC1    ' SDA rauf, bzw. freigeben
	RCALL	Idle_Call    ' warten
	CBI	DDRC,DDC0    ' SCL rauf 
L_0x011A:
	SBIS	PINC,PINC0   ' stretch ? 
	RJMP	L_0x011A     
	CLT                  ' löschen T-Bit
	SBIC	PINC,PINC1   ' ACK oder nicht ? 
	SET                  ' kein ACK, also T-Bit setzen
	BLD	r6,2         ' so oder so, das T-Bit in das "ERR" Bit
	RJMP	Idle_Call    ' warten (und fertig)

Autor

Benutzer:PicNick

Siehe auch


LiFePO4 Speicher Test