(→IO-Register) |
Willa (Diskussion | Beiträge) K (→SERVO) |
||
(Eine dazwischenliegende Version von einem anderen Benutzer wird nicht angezeigt) | |||
Zeile 1.050: | Zeile 1.050: | ||
</pre> | </pre> | ||
==SERVO== | ==SERVO== | ||
− | Damit können bis zu 16 Servos an beliebigen Port-Pins versorgt werden. Bascom nimmt dazu einen Timer in Beschlag (defaultmäßig Timer0). Für zwei Servos (Beispiel) wird konfiguriert: | + | 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 | 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. | "Reload" bestimmt die Auflösung, die Angabe "10" für 10µS wird empfohlen. | ||
Zeile 1.160: | Zeile 1.160: | ||
**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 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). | **Solange "Zähler >= 256" wird nur mehr gezählt, bis es Zeit für einen Refresh ist (s.o). | ||
+ | |||
==I2C Software== | ==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. | Für AVRs ohne Hardware-TWI bietet Bascom eine Reihe I2c Funktionen an. Die SDA & SCL Pins können beliebig gewählt werden. | ||
Zeile 1.279: | Zeile 1.280: | ||
==Siehe auch== | ==Siehe auch== | ||
+ | * [[Bascom]] | ||
* [[Bascom Inside]] | * [[Bascom Inside]] | ||
* [[Bascom Libraries]] | * [[Bascom Libraries]] | ||
− | |||
[[Kategorie:Microcontroller]] | [[Kategorie:Microcontroller]] | ||
[[Kategorie:Grundlagen]] | [[Kategorie:Grundlagen]] |
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
Inhaltsverzeichnis
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)