Forum: Mikrocontroller und Digitale Elektronik Subtraktion mit Mega8 von nicht ganzzahligen Zahlen


von Hans B. (Gast)


Lesenswert?

Hi,

ich wollte mit dem AVR in Assembler eine Zahl subtrahieren und schreib 
einfach mal das Beispiel hin

subi XL, low(1,2)
sbci XH, high(1,2)

Jetzt kommt aber beim assemblieren die Fehlermeldung
" syntax error, unexpected ',' "

Wie kann man denn auch ','-Zahlen subtrahieren?

Gruß

von Gerhard (Gast)


Lesenswert?

Dir ein Format überlegen, wie du deine nicht-Integerzahlen auf 
8bit-Register abbilden kannst. Dann überlegen, welche Operation du dazu 
auf diese Register anwenden musst und das ganze in Assembler 
assemblieren.

Ein 8bit-µC kennt nativ keine Kommazahlen.

von Gerhard (Gast)


Lesenswert?

-assemblieren
+implementieren

von Hans B. (Gast)


Lesenswert?

Hi,

ahso, ok. dann multiplizier ich das erst, zieh dann ab und dividiere am 
Ende wieder. Danke soweit :-)


Jetzt wollte ich einfach mal, bei dem Programm, dass die Spannung misst 
und anzeigt von der Seite hier, was abziehen.
Das sieht so aus:

.include "m8def.inc"

.def z0        = r1          ; Zahl für Integer -> ASCII Umwandlung
.def z1        = r2
.def z2        = r3
.def z3        = r4
.def temp1     = r16         ; allgemeines Register, zur kurzfristigen 
Verwendung
.def temp2     = r17         ; Register für 24 Bit Addition, 
niederwertigstes Byte (LSB)
.def temp3     = r18         ; Register für 24 Bit Addition, mittlerers 
Byte
.def temp4     = r19         ; Register für 24 Bit Addition, 
höchstwertigstes Byte (MSB)
.def adlow     = r20         ; Ergebnis vom ADC-Mittelwert der 256 
Messungen
.def adhigh    = r21         ; Ergebnis vom ADC-Mittelwert der 256 
Messungen
.def messungen = r22         ; Schleifenzähler für die Messungen
.def zeichen   = r23         ; Zeichen zur Ausgabe auf den UART
.def temp5     = r24
.def temp6     = r25

; Faktor für Umrechung des ADC-Wertes in Spannung
; = (Referenzspannung / 1024 ) * 100000
; = 5V / 1024 * 1.000.000
.equ Faktor = 4883

.equ F_CPU = 4000000                            ; Systemtakt in Hz
.equ BAUD  = 9600                               ; Baudrate

; Berechnungen
.equ UBRR_VAL   = ((F_CPU+BAUD*8)/(BAUD*16)-1)  ; clever runden
.equ BAUD_REAL  = (F_CPU/(16*(UBRR_VAL+1)))     ; Reale Baudrate
.equ BAUD_ERROR = ((BAUD_REAL*1000)/BAUD-1000)  ; Fehler in Promille

.if ((BAUD_ERROR>10) || (BAUD_ERROR<-10))       ; max. +/-10 Promille 
Fehler
  .error "Systematischer Fehler der Baudrate grösser 1 Prozent und damit 
zu hoch!"
.endif

; RAM
.dseg
.org 0x60
Puffer: .byte 10

; hier geht das Programm los
.cseg
.org 0

    ldi     temp1, LOW(RAMEND)                  ; Stackpointer 
initialisieren
    out     SPL, temp1
    ldi     temp1, HIGH(RAMEND)
    out     SPH, temp1

;UART Initalisierung

    ldi     temp1, LOW(UBRR_VAL)                ; Baudrate einstellen
    out     UBRRL, temp1
    ldi     temp1, HIGH(UBRR_VAL)
    out     UBRRH, temp1

    sbi     UCSRB, TXEN                         ; TX einschalten

; ADC initialisieren: Single Conversion, Vorteiler 128
; Kanal 0, interne Referenzspannung AVCC

    ldi     temp1, (1<<REFS0)
    out     ADMUX, temp1
    ldi     temp1, (1<<ADEN) | (1<<ADPS2) | (1<<ADPS1) | (1<<ADPS0)
    out     ADCSRA, temp1

Hauptschleife:
    clr     temp1
    clr     temp2
    clr     temp3
    clr     temp4

    ldi     messungen, 0        ; 256 Schleifendurchläufe

; neuen ADC-Wert lesen  (Schleife - 256 mal)

adc_messung:
    sbi     ADCSRA, ADSC        ; den ADC starten

adc_warten:
    sbic    ADCSRA, ADSC        ; wenn der ADC fertig ist, wird dieses 
Bit gelöscht
    rjmp    adc_warten

; ADC einlesen:

    in      adlow, ADCL         ; immer zuerst LOW Byte lesen
    in      adhigh, ADCH        ; danach das mittlerweile gesperrte High 
Byte

; alle 256 ADC-Werte addieren
; dazu wird mit den Registern temp4, temp3 und temp2 ein
; 24-Bit breites Akkumulationsregister gebildet, in dem
; die 10 Bit Werte aus adhigh, adlow aufsummiert werden

    add     temp2, adlow        ; addieren
    adc     temp3, adhigh       ; addieren über Carry
    adc     temp4, temp1        ; addieren über Carry, temp1 enthält 0
    dec     messungen           ; Schleifenzähler MINUS 1
    brne    adc_messung         ; wenn noch keine 256 ADC Werte -> 
nächsten Wert einlesen

; Aus den 256 Werten den Mittelwert berechnen
; Bei 256 Werten ist das ganz einfach: Das niederwertigste Byte
; (im Register temp2) fällt einfach weg
;
; allerdings wird der Wert noch gerundet

    cpi     temp2,128           ; "Kommastelle" kleiner als 128 ?
    brlo    nicht_runden        ; ist kleiner ==> Sprung

; Aufrunden
    subi    temp3, low(-1)      ; addieren von 1
    sbci    temp4, high(-1)     ; addieren des Carry

nicht_runden:

;   Ergebnis nach adlow und adhigh kopieren
;   damit die temp Register frei werden

    mov     adlow, temp3
    mov     adhigh, temp4

; in Spannung umrechnen

    ldi     temp5,low(Faktor)
    ldi     temp6,high(Faktor)
    rcall   mul_16x16

; in ASCII umwandeln

    ldi     XL, low(Puffer)
    ldi     XH, high(Puffer)
    rcall   Int_to_ASCII

;an UART Senden

    ldi     ZL, low(Puffer+3)
    ldi     ZH, high(Puffer+3)
    ldi     temp1, 1
    rcall   sende_zeichen       ; eine Vorkommastelle ausgeben

    ldi     zeichen, ','        ; Komma ausgeben
    rcall   sende_einzelzeichen

    ldi     temp1, 3            ; Drei Nachkommastellen ausgeben
    rcall   sende_zeichen

    ldi     zeichen, 'V'        ; Volt Zeichen ausgeben
    rcall   sende_einzelzeichen

    ldi     zeichen, 10         ; New Line Steuerzeichen
    rcall   sende_einzelzeichen

    ldi     zeichen, 13         ; Carrige Return Steuerzeichen
    rcall   sende_einzelzeichen

    rjmp    Hauptschleife

; Ende des Hauptprogramms

; Unterprogramme

 ; ein Zeichen per UART senden

sende_einzelzeichen:
    sbis    UCSRA,UDRE          ; Warten, bis UDR bereit ist ...
    rjmp    sende_einzelzeichen
    out     UDR, zeichen        ; und Zeichen ausgeben
    ret

; mehrere Zeichen ausgeben, welche durch Z adressiert werden
; Anzahl in temp1

sende_zeichen:
    sbis    UCSRA,UDRE          ; Warten, bis UDR bereit ist ...
    rjmp    sende_zeichen
    ld      zeichen, Z+         ; Zeichen laden
    out     UDR, zeichen        ; und Zeichen ausgeben
    dec     temp1
    brne    sende_zeichen
    ret

; 32 Bit Zahl in ASCII umwandeln
; Zahl liegt in temp1..4
; Ergebnis ist ein 10stelliger ASCII String, welcher im SRAM abgelegt 
wird
; Adressierung über X Pointer
; mehrfache Subtraktion wird als Ersatz für eine Division durchgeführt.

Int_to_ASCII:

    push    ZL                      ; Register sichern
    push    ZH
    push    temp5
    push    temp6

    ldi     ZL,low(Tabelle*2)       ; Zeiger auf Tabelle
    ldi     ZH,high(Tabelle*2)
    ldi     temp5, 10               ; Schleifenzähler

Int_to_ASCII_schleife:
    ldi     temp6, -1+'0'           ; Ziffernzähler zählt direkt im 
ASCII Code
    lpm     z0,Z+                   ; Nächste Zahl laden
    lpm     z1,Z+
    lpm     z2,Z+
    lpm     z3,Z+

Int_to_ASCII_ziffer:
    inc     temp6                   ; Ziffer erhöhen
    sub     temp1, z0               ; Zahl subrahieren
    sbc     temp2, z1               ; 32 Bit
    sbc     temp3, z2
    sbc     temp4, z3
    brge    Int_to_ASCII_ziffer     ; noch kein Unterlauf, nochmal

    add     temp1, z0               ; Unterlauf, eimal wieder addieren
    adc     temp2, z1               ; 32 Bit
    adc     temp3, z2
    adc     temp4, z3
    st      X+,temp6                ; Ziffer speichern
    dec     temp5
    brne    Int_to_ASCII_schleife   ; noch eine Ziffer?

    pop     temp6
    pop     temp5
    pop     ZH
    pop     ZL                      ; Register wieder herstellen
    ret

; Tabelle mit Zahlen für die Berechung der Ziffern
; 1 Milliarde bis 1
Tabelle:
.dd 1000000000, 100000000, 10000000, 1000000, 100000, 10000, 1000, 100, 
10, 1

; 16 Bit Wert in Spannung umrechnen
;
; = 16Bitx16Bit=32 Bit Multiplikation
; = vier 8x8 Bit Multiplikationen
;
; adlow/adhigh * temp5/temp6

mul_16x16:
    push    zeichen
    clr     temp1                   ; 32 Bit Akku löschen
    clr     temp2
    clr     temp3
    clr     temp4
    clr     zeichen                 ; Null, für Carry-Addition

    mul     adlow, temp5            ; erste Multiplikation
    add     temp1, r0               ; und akkumulieren
    adc     temp2, r1

    mul     adhigh, temp5           ; zweite Multiplikation
    add     temp2, r0               ; und gewichtet akkumlieren
    adc     temp3, r1

    mul     adlow, temp6            ; dritte Multiplikation
    add     temp2, r0               ; und gewichtet akkumlieren
    adc     temp3, r1
    adc     temp4, zeichen          ; carry addieren

    mul     adhigh, temp6           ; vierte Multiplikation
    add     temp3, r0               ; und gewichtet akkumlieren
    adc     temp4, r1

    pop     zeichen
    ret



Das ganze müsste ich doch zwischen ";in ASCII umwandeln" und ";an UART 
senden" einfügen, oder? Und die Zahl liegt doch im Register XL und XH?
also, wenn man mal die ','-Zahlen weglässt:

subi XL, low(1)
sbci XH, high(1)

aber das Programm funktioniert dann nicht mehr richtig.

Wär super, wenn mal jemand drüber schauen könnte.


Gruß

von oha (Gast)


Lesenswert?

Als ASM Programmierer sollte man von Floatingpointzahlen abstand nehmen. 
Ein gebraeuchlicher Weg ist zB mit hundertsteln zu arbeiten, dann wird 
aus 1.2 naemlich 1200 und dann des weiteren mit 16 oder 24 bit Ganzzahl 
zu arbeiten.

von Falk B. (falk)


Lesenswert?


Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.