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
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 Funktions-code
- 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 Portb.7 , Reset 'wait until bit 7 of Port B is 0.
- Code
L_0x008C: SBIC PORTB,PB7 RJMP L_0x008C
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
L_0x009C: SBIW ZL,0x0001 BRNE L_0x009C RET
- Set_ErrBit
SET BLD r6,2 RET
- Clear_ErrBit
CLT BLD r6,2 RET
- MakeMask
LDI r25,0x01 AND r24,r24 BREQ L_0x00BC CLC L_0x00B6: ROL r25 DEC r24 BRNE L_0x00B6 L_0x00BC: MOV r24,r25 COM r25 RET
- 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 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 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 L_0x00F8: CLR XL // clear Timout Lo CLR XH // clear Timout Hi L_0x00FC: PUSH ZL // Save PUSH ZH LDI ZL,0x20 // calc from $XTAL LDI ZH,0x00 CALL L_0x009C // 10 µS Idle POP ZH // Restore POP ZL LDD r0,Z + 0 // PIND AND r0,r24 // PIND & Mask EOR r0,r16 // (PIND & Mask) ^ State BRNE L_0x011C // OK, Pulsein done ********* ADIW XL,0x0001 // PulseCounter++ BRNE L_0x00FC // cont'd Loop L_0x0118: CALL Set_ErrBit L_0x011C: MOV r24,XL // result --> R24:r25 MOV r25,XH RET // that's it
ENCODER
- Aufruf
DIM Result AS BYTE Result = ENCODER(pind.1 , Pind.3 , Leftlabel , Rightlabel , 0) ... ... Leftlabel: Return Rightlabel: Return
- Code
LDI XL,0x60 ; result LDI XH,0x00 CLT ; clear t-bit (no wait) L_0x008C: LD r20,X ; Lesen Result (vorheriger Pin-Wert) IN r16,PIND CLR r24 ; clear SBRC r16,1 ; pind.1 ORI r24,0x01 ; A = 1 SBRC r16,3 ; pind.3 ORI r24,0x02 ; B = 2 CP r24,r20 ; <> previous BRNE L_0x00A2 ; difference BRTS L_0x008C ; t-bit ? ( wait) RJMP L_0x00C2 ; no diff, no wait -> xit L_0x00A2: ST X,r24 ; store new as old SWAP r20 ; OLD ADD r24,r20 ; OLD + NEW 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: RET Rightlabel: RET
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.
- 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 'daten-empfangs adresse ("MyString") LDI XH,0x00 CLR r24 '= 0 ST -Y,r24 LDI ZL,0x31 ' Adresse von DDRD für PinD LDI r24,0x00 ' CLT ' Polarität CALL L_0x02BE ' CALL der eigentlichen "SERIN" Funktion
Einige Hilfs-Funktionen
'----------------------------------------- ; Makemask r24 Bit# --> Mask ->r24 !Mask ->r25 '----------------------------------------- L_0x01CC: LDI r25,0x01 AND r24,r24 BREQ L_0x01DA CLC L_0x01D4: ROL r25 DEC r24 BRNE L_0x01D4 L_0x01DA: MOV r24,r25 COM r25 RET '----------------------------------------- ' Odd-Even PARITY '----------------------------------------- L_0x01E0: PUSH r16 PUSH r24 LDI r16,0x08 CLR r25 L_0x01E8: ROL r24 BRCC L_0x01EE INC r25 L_0x01EE: DEC r16 BRNE L_0x01E8 POP r24 POP r16 RET '----------------------------------------- ' stringleng ASCIZ R24 = strlen( @X ) '----------------------------------------- L_0x0202: LDI r24,0xFF L_0x0204: INC r24 LD r25,X+ AND r25,r25 BRNE L_0x0204 RET
Die Funktion
'----------------------------------------- 'SERIN '----------------------------------------- L_0x02BE: RCALL L_0x01CC makemask CLR ZH LDD r0,Z + 0 DDRD AND r0,r25 Make Input STD Z + 0,r0 INC ZL PORTD LDD r0,Z + 0 AND r0,r25 STD Z + 0,r0 Clear PORTD.x SBIW ZL,0x0002 ->PIND MOV r16,r24 Mask MOV r22,r25 !Mask LD r20,Y+ Byte count MOV r19,r20 L_0x02DA: LDD r17,Y + 0 Bit (8) CPI r17,0x07 7 Bit ? BRNE L_0x02F2 '---- 7 Bits LDD r17,Y + 1 parity CPI r17,0x00 BRNE L_0x02EC LDD r18,Y + 2 stopp SUBI r18,0xF9 no Parity RJMP L_0x0302 L_0x02EC: LDD r18,Y + 2 stopp SUBI r18,0xF8 + Parity RJMP L_0x0302 '---- 8 Bits L_0x02F2: LDD r17,Y + 1 parity CPI r17,0x00 BRNE L_0x02FE -->yes LDD r18,Y + 2 stopp SUBI r18,0xF8 no parity RJMP L_0x0302 L_0x02FE: LDD r18,Y + 2 stopp SUBI r18,0xF7 + parity L_0x0302: MOV r21,r18 '---------------------------------------- ' Byte Loop R18 /R21 bits X data pntr Z PIN(d) T-Bit revert Y Parameter r20 Len r19 String '---------------------------------------- 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 continue wait loop L_0x030E: 'reverted BRNE L_0x0312 high--> start RJMP L_0x0304 continue wait loop L_0x0312: RCALL L_0x035E 1/2 Bit L_0x0314: RCALL L_0x035E 1/2 Bit 2 + RCALL L_0x035E 1/2 Bit 2 + CLC clear carry 1 LDD r0,Z + 0 Rx Pin 2 AND r0,r16 Mask 1 BREQ L_0x0322 =0 1/2 SEC set carry 1 L_0x0322: DEC r18 count bits 1 BREQ L_0x032C 1/2 ROR r24 shift in carry r24:r25 1 ROR r25 1 RJMP L_0x0314 cont'd loop 2 L_0x032C: CPI r21,0x0A bit-size <> 10 ? BRLO L_0x033A < SUBI r21,0x09 -9 L_0x0332: ROL r25 adjust data ->right ROL r24 DEC r21 BRNE L_0x0332 L_0x033A: BRTC L_0x033E COM r24 revert data Byte L_0x033E: LDD r17,Y + 0 CPI r17,0x07 BRNE L_0x0346 ANDI r24,0x7F clear 2^^7 L_0x0346: ST X+,r24 store data CPI r19,0x00 string ? BRNE L_0x0358 no CPI r24,0x0D yes = <CR> BRNE L_0x02DA no, continue CLR r24 yes, make ASCIZ & exit ST X,r24 0x00 L_0x0354: ADIW YL,0x0003 restore Y RET exit L_0x0358: DEC r20 count BREQ L_0x0354 all done RJMP L_0x02DA continue '------------------------------------------- ' 1/2 Bit Delay '------------------------------------------- L_0x035E: PUSH r24 2 PUSH r25 2 LDS r24,0x006F 2 LDS r25,0x0070 2 L_0x036A: SBIW r24,0x0001 1 BRNE L_0x036A 1/2 POP r25 2 POP r24 2 RET 4