Forum: Mikrocontroller und Digitale Elektronik Sauberes Programmieren


von Pat R. (patrik)


Lesenswert?

Hallo Leute,
ich bin noch relativ neu in Sachen uC Programmieren und habe ein kleines 
Problemchen mit dem ich nicht klar komme. Etwas unten seht ihr den Code 
den ich geschrieben habe, allerdings funct er nicht so wie er funcen 
sollte. Der uC dreht nämlich durch und spring an Stellen wo er nichts zu 
suchen hat. Seit zwei Tagen versuche ich das auf die Reihe zu kriegen 
aber ohne Erfolg. Ich habe zwei verschiedene uC(ATmega16) ausprobiert 
und beide machen dasselbe. Also ich denke, irgendwas habe ich nicht 
richtig programmiert oder vergessen, habe aber keine Ahnung was.

Der Code sollte folgendes machen: ich schicke im über UART zeichen und 
er gibt es seriel weiter und erzeung einen clock(soll einen fpga 
proggen).

 Was macht also der uC? Diese Version des Codes macht z.B das, dass es 
in die Methode do_done springt aus der Main Methode(dort wo so viele 
rjmp write sind), wobei PIND7 ist direkt mit Masse verbunden. Wenn ich 
dort nicht nur einmal rjmp write hatte, ging er zur end Marke wo er 
nirgendswie dürfte kommen können. Ich habe auch andere Versionen 
ausprobiert und hatte immer irgendwelche ähnliche Ergebnisse. Daher 
denke ich mir, dass es Fehler in meinem Code sind.

Wenn sich also jeman den Code anschauen würde und mir ein Paar 
Vorschläge geben würde, wie ich meinen ProgStyle verbessern kann, wäre 
ich ihm dankbar.
danke
patrik




.include "m16def.inc"

;.dseg Sequenz
;----------------------------------------------------------------------- 
----------------------------------------

.dseg


; end .dseg Sequenz
;----------------------------------------------------------------------- 
----------------------------------------


;.cseg Sequenz
;----------------------------------------------------------------------- 
----------------------------------------

.cseg

; end .cseg Sequenz
;----------------------------------------------------------------------- 
----------------------------------------


;.equ Sequenz
;----------------------------------------------------------------------- 
----------------------------------------

.equ CLOCK = 16000000              ; eigene Frequenz
.equ BAUD = 9600                ; die Baudrate mit der man kommunizieren 
will
.equ UBRRVAL = CLOCK/(BAUD*16)-1


.equ cCTS = 4                  ; CTS Pin
;.equ cRTS = 5                  ; RTS Pin



.equ cINIT_B = 0
.equ STATUS_INIT_B = (1 << cINIT_B)

.equ cPROG_B = 1
.equ STATUS_PROG_B = (1 << cPROG_B)

.equ cDONE = 2
.equ STATUS_DONE = (1 << cDONE)

.equ cSTARTED = 3
.equ STATUS_STARTED = (1 << cSTARTED)

.equ cCRC_ERROR = 4
.equ STATUS_CRC_ERROR = (1 << cCRC_ERROR)

.equ cTX = 5
.equ TX = (1 << cTX)

.equ cNEW_CHAR = 6                ; signalisiert, dass ein Zeichen im 
eingelesen wurde
.equ NEW_CHAR = (1 << cNEW_CHAR)


; A0 -> CCLK nur output
; A1 -> DATA nur output
; A2 -> PROG_B nur output
.equ CCLK = 0
.equ DATA = 1
.equ PROG_B = 2

; B2 -> INIT_B nur input/output
; B3 -> DONE nur input
.equ INIT_B = 2
.equ DONE = 3


.def temp = r16

.def DATA_REG = r17

.def STATUS_REGISTER = r18


; end .equ Sequenz
;----------------------------------------------------------------------- 
----------------------------------------




.org   0x00
        rjmp init

.org   INT0addr  ; Zuweisung der grossen/kleinen Ziffer an rpm bzw. 
speed
    rjmp int_init_b

.org   INT1addr
    rjmp int_done

.org   OC2addr
        reti

.org  OVF2addr  ;Overflow2 Interrupt Vector Address
    reti

.org  ICP1addr  ;Input Capture1 Interrupt Vector Address
    reti

.org  OC1Aaddr  ;Output Compare1A Interrupt Vector Address
    reti

.org  OC1Baddr  ;Output Compare1B Interrupt Vector Address
    reti

.org   OVF1addr  ;Interruptvektor für TimerOverFlow1
    reti

.org   OVF0addr    ;Interruptvektor für TimerOverFlow0
        reti

.org  SPIaddr   ;SPI Interrupt Vector Address
    reti

.org   URXCaddr    ;Interruptvektor für UART-Empfang
        rjmp int_rxc

.org  UDREaddr  ;UART Data Register Empty Interrupt Vector Address
    reti

.org  UTXCaddr  ;UART Transmit Complete Interrupt Vector Address
    rjmp utx_complete

.org   ADCCaddr    ;Interruptvektor für AD-Wandler
        reti

.org  ERDYaddr  ;EEPROM Interrupt Vector Address
    reti

.org  ACIaddr   ;Analog Comparator Interrupt Vector Address
    reti

.org    TWIaddr     ;Irq. vector address for Two-Wire Interface
    reti

.org  INT2addr    ;External Interrupt2 Vector Address
    reti

.org   OC0addr     ;Interruptvektor für TimerOverFlow
        reti

.org  SPMRaddr    ;Store Program Memory Ready Interrupt Vector Address
    reti



init:

;----------------------------------------------------------------------- 
-----------
; Sicherheitshalber löschen wir alle Register, weil sie irgendeine Werde 
haben können,
; die in diese Register geschrieben wurden, bevor der uC resetet wurde

; hier wird nur clr r0-31 ausgeführt
.include "clear_regs.def"

;----------------------------------------------------------------------- 
-----------

  ; Stack initialisieren
    ldi temp, LOW(RAMEND)
    out SPL, temp
    ldi temp, HIGH(RAMEND)
    out SPH, temp


    ; Baudrate einstellen
    ldi temp, LOW(UBRRVAL)
    out UBRRL, temp

    ldi temp, HIGH(UBRRVAL)
    out UBRRH, temp

  ;dies Einstellung entspricht 1 Start Bit, 8 Data Bit, 1 Stop Bit, kein 
Parity Bit
    ldi temp, (1<<URSEL)|(3<<UCSZ0)
    out UCSRC, temp


  ldi temp, (1<<RXEN)|(1<<TXEN)|(1<<TXCIE)|(1<<RXCIE)
  out UCSRB, temp



  ; A0 -> CLK nur output
  ; A1 -> DATA nur output
  ; A2 -> PROG_B nur output ohne Transistor da nur auf LOW ziehen und 
dann das Pin als input konfigurieren
  ; B2 -> INIT_B nur input/output
  ; B3 -> DONE nur input

  ; nur die Pins 1 und 2 des PORTD sind Ausgänge
  ; Pin 0 ist RXD, Pin 1 ist TXD, Pin 5 ist RTS, Pin 4 ist CTS(Schaltung 
nach SerialPA.sch CTS und RTS eigentlich umgekehrt aber es funct!)
  ldi temp, 0b00010010
  out DDRD, temp

  ldi temp, 0b00000000
  out DDRA, temp

  ser temp
  out DDRB, temp

  sei

;----------------------------------------------------------------------- 
-----------
; start main
start:

  clr STATUS_REGISTER

  clr temp
  out DDRA, temp



  cbi PORTD,  cCTS

  rcall waitForChar     ; warte auf das erste Zeichen vom PC



  ldi temp, 0b00000111   ; wenn das erste Zeichen kommt, werden PIN A0-3 
als Ausgang konfiguriert
  out DDRA, temp

  sbi PORTA, CCLK       ; CCLK muss auf HIGH gesetzt werden und PROG_B 
auf mindestens 300ns auf 0!!!
  cbi PORTA, PROG_B


  sbr STATUS_REGISTER, STATUS_PROG_B ; hier wird vermerkt, dass PROG_B 
und INIT_B auf LOW gezogen wurde

wait_for_init_b_low:
  sbic PIND, INIT_B    ; wenn INIT_B LOW ist, gehe zur LowBearbeitung
  rjmp wait_for_init_b_low


  ldi temp, 50      ; für 300ns brauchen wir cca 50 Taktzycklen bei 
16MHz

wait_prog_b:
  dec temp
  breq end_prog_b
  rjmp wait_prog_b

end_prog_b:

  ldi temp, 0b00000011  ; nach 300ns muss PROG_B released werden, d.h. 
PIN A2 als Input konfigurieren
  out DDRA, temp

  sbi PORTA, CCLK       ; CCLK muss auf HIGH gesetzt werden und PROG_B 
auf mindestens 300ns auf 0!!!


wait_for_init_b_high:      ; hier wird gewartet, bis INIT_B wieder auf 
HIGH geht
  sbis PIND, INIT_B    ; wenn INIT_B LOW ist, gehe zur LowBearbeitung
  rjmp wait_for_init_b_high


  cbr STATUS_REGISTER, STATUS_INIT_B ; Nachdem INIT_B auf HIGH ist, kann 
die Konfiguration anfangen
    cbr STATUS_REGISTER, STATUS_PROG_B


  sbr STATUS_REGISTER, STATUS_STARTED ; Proggen kann starten

  ser DATA_REG
  rcall sendData

  ldi temp, (1<<ISC01)
  out MCUCR, temp

  in temp, GIFR
  ori temp, (1<<INTF0)
  out GIFR, temp


  ; Interrupt0  aktivieren
  ldi temp, (1<<INT0)
  out GICR, temp

write:
  sbic PIND, 7
  rcall do_done


  ; HIER DREHT DER uC DURCH D.H. ER SPRING IN METHODEN WO ER NICHTS ZU 
SUCHEN HAT

  sbis PIND, INIT_B
  sbi PORTB, 1  ; crc error

  nop
  nop
  nop


  rjmp write
  rjmp write
  rjmp write
  rjmp write
  rjmp write
  rjmp write
  rjmp write


end:
  rjmp write

error:
  rjmp write


; end main
;----------------------------------------------------------------------- 
-----------






int_init_b:
reti

waitForChar:
  sbrs STATUS_REGISTER, cNEW_CHAR
  rjmp waitForChar
ret

; Interruptroutine: wird ausgeführt sobald ein Byte über das UART 
empfangen wurde
int_rxc:
    in DATA_REG, UDR

    sbrs STATUS_REGISTER, cNEW_CHAR
    rjmp end_uart

    rcall write_char

end_uart:
  sbr STATUS_REGISTER, NEW_CHAR
reti           ; Interrupt beenden

wait:
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop

ret

write_char:
    sbrs DATA_REG, 7     ; setze das Bit 7
    cbi  PORTA, DATA

    sbrc DATA_REG, 7
    sbi  PORTA, DATA

    cbi PORTA, CCLK      ; das Bit 7 übernehmen

    rcall wait

    sbi PORTA, CCLK
    rcall wait


    sbrs DATA_REG, 6     ; setze das Bit 6
    cbi  PORTA, DATA

    sbrc DATA_REG, 6
    sbi  PORTA, DATA

    cbi PORTA, CCLK      ; das Bit 6 übernehmen
    rcall wait

    sbi PORTA, CCLK
    rcall wait


    sbrs DATA_REG, 5     ; setze das Bit 5
    cbi  PORTA, DATA

    sbrc DATA_REG, 5
    sbi  PORTA, DATA

    cbi PORTA, CCLK      ; das Bit 5 übernehmen
    rcall wait

    sbi PORTA, CCLK
    rcall wait


    sbrs DATA_REG, 4     ; setze das Bit 4
    cbi  PORTA, DATA

    sbrc DATA_REG, 4
    sbi  PORTA, DATA

    cbi PORTA, CCLK      ; das Bit 4 übernehmen
    rcall wait

    sbi PORTA, CCLK
    rcall wait

    sbrs DATA_REG, 3     ; setze das Bit 3
    cbi  PORTA, DATA

    sbrc DATA_REG, 3
    sbi  PORTA, DATA

    cbi PORTA, CCLK      ; das Bit 3 übernehmen
    rcall wait

    sbi PORTA, CCLK
    rcall wait


    sbrs DATA_REG, 2     ; setze das Bit 2
    cbi  PORTA, DATA

    sbrc DATA_REG, 2
    sbi  PORTA, DATA

    cbi PORTA, CCLK      ; das Bit 2 übernehmen
    rcall wait

    sbi PORTA, CCLK
    rcall wait



    sbrs DATA_REG, 1     ; setze das Bit 1
    cbi  PORTA, DATA

    sbrc DATA_REG, 1
    sbi  PORTA, DATA

    cbi PORTA, CCLK      ; das Bit 1 übernehmen
    rcall wait

    sbi PORTA, CCLK
    rcall wait



    sbrs DATA_REG, 0     ; setze das Bit 0
    cbi  PORTA, DATA

    sbrc DATA_REG, 0
    sbi  PORTA, DATA

    cbi PORTA, CCLK      ; das Bit 0 übernehmen
    rcall wait

    sbi PORTA, CCLK
    rcall wait

ret

do_done:
  ldi temp, 0b00000001   ; wenn das erste Zeichen kommt, werden PIN A0-3 
als Ausgang konfiguriert
  out DDRA, temp

  ; Interrupt0 bei sinkender Flanke, Interrupt1 nur bei steigender 
Flanke
  ldi temp, (1<<ISC01) | (1<<ISC10) | (1<<ISC11)
  out MCUCR, temp

  ; Interrupt0 und Interrupt1 aktivieren
  ldi temp, (1<<INT1)
  out GICR, temp
  sbi PORTB, 7

do_done1:

  sbis PINB, INIT_B
  sbi PORTB, 1    ; crc error

  cbi PORTA, CCLK      ; ein DONE Zycklus
    rcall wait

  sbi PORTA, CCLK
    rcall wait

  rjmp do_done1

ret

int_done:

  sbi PORTB, 0    ; done
reti

sendData:
; Wait for empty transmit buffer


  sbis UCSRA, UDRE
  rjmp sendData

  sbr STATUS_REGISTER, TX

  out UDR, DATA_REG

ret

utx_complete:
  cbr STATUS_REGISTER, TX
reti

von Pat R. (patrik)


Lesenswert?

Zusatz: ich dachte an sowas wie 'wenn du in eine Methode/Interrupt 
kommst musst du solche und solche Register so und so sichern' oder 
ähnliches

von Ralph (Gast)


Lesenswert?

Warum ASM ???????

Verwende C und schon kann jemand der deinen Code nicht kennt, das ganze 
in kurzer Zeit verstehen.

Dann hast du auch größere Chancen, vernünftige Hilfen zu bekommen.

Was aber auf jedenfall fehlt, sind die Instructions PUSH und POP bei 
Funktionsbeginn und Funkionsende.

ASM zu benutzen gehört in dem Bereich SadoMaso ...



Gruß
Ralph

von rene (Gast)


Lesenswert?

Kein push ? Kein pop ? Aber viel schlimmer ist dass das Statusregister 
nicht gesavt wurde. Generell wird zuviel rumgesprungen in der 
interruptroutine. Und ein wait gibt es eh nicht im interrupt.

Als Anregung, ein paar Seiten, allerdings ohne interrupt
http://www.ibrtses.com/embedded/avr.html

P.

von Michael U. (Gast)


Lesenswert?

Hallo,

ich habe da noch nicht so richtig reingeschaut, aber 2 Anmerkungen:
Du hast schnelle 16MHZ, bei mir dauert ein Takt bei 16MHz 62,5ns, für 
300 brauche also rund 4 und nicht 50. Außerdem wartet Deine Schleife pro 
Durchlauf 4 Takte, macht * 50 *62,5 bei mir 12500 ns oder 12,5µs.

Außerdem: warum sollte man nach Reset alle Register auf 0 setzen?
Der Inhalt ist ohnehin nach Reset zufällig, wenn ein Register im 
Programmlauf benutzt wird, mu0 sowiesod er passende Wert geladen werden, 
der wird kaum bei allen Registern 0 sein. Für mich also nutzlos, auch 
wenn es nicht stört.


Zur Interruptroutine: Du sichert das Statusregister nicht, das bringt in 
jedem Fall das Hauptprogramm aus dem Konzept, wenn ein IRQ z.B. genau 
zwischen einem CPI und dem davon abhängigem BRNE erfolgt und in der ISR 
das Z-Flag geändert wird... Kann nicht lange gut gehen, SREG sichern!
Außerdem rufst Du eine ziemlich lange write_char innerhalb der ISR auf. 
Was passiert, wenn die länger braucht und das nächste Zeichen ist schon 
da?

Gruß aus Berlin
Michael

von Otto (Gast)


Lesenswert?

Hallo Patrik,

1. zerlege Dein Programm zunächst in übersichtliche
   kleine Schritte, die jeweils eine Funktion erfüllen
   und prüfe diese Funktion.

2. Achte dabei darauf, nach Möglichkeit "temporäre"
   Register zu verwenden.

3. Rufe diese Funktionen dann so nacheinander auf,
   wie es für den Funktionsablauf erforderlich ist.

4. Prüfe, ob Du mit Schleifen Vereinfachung erreichen
   kannst (z. B. Deine "wait - Funktion").

Gruss Otto

von Michael U. (Gast)


Lesenswert?

Hallo,

@Ralph: sorry, aber Du schreibst schlicht Blödsinn!

Durcheinander und ungünstige Strukturen und zu sparsame Kommentare kann 
man auch in C basteln.

Mich stört eher, daß in seinen "Methoden" so wenig Methode ist. ;)
Dazu kommt, daß hier offenbat was mit Hadrware gemacht wird, zu dem (wie 
meist) zumindest ein Schaltplanauszug oder eine verständlichere 
Beschreibung von Port- und Pinbenutzung fehlt, damit es Außenstehende 
überhaupt nachvollziehen können, was passieren soll.

Gruß aus Berlin
Michael


von Pat R. (patrik)


Lesenswert?

Hallo Jungs,
erstmals vielen Dank für euere Ratschläge, ich versuche das Ding bissl 
umzubauen. Ich hatte viel sauberere Versionen davon, aber ohne Sichern 
des SREG's denke ich komme wirklich nicht weit.

von rene (Gast)


Lesenswert?

Patrik,
Ich hab ein Beispiel hochgeladen.
http://www.ibrtses.com/embedded/avrasmuartint.html

rene

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.