Forum: Mikrocontroller und Digitale Elektronik Code von ganz besonderem Lauflicht vereinfachen


von Karl (Gast)


Lesenswert?

Hallo zusammen,

ich habe mir mit dem Mega8 (und mit einigen wertvollen Hinweisen aus
diesem Forum) ein Lauflicht in Assembler programmiert.

Aber der Code hat zehn Codeabschnitte, die sich bis auf ein paar
Parameter sehr ähnlich sehen. Das schreit nach einer Prozedur mit drei
Übergabeparametern, aber es funktioniert leider dann nicht mehr.

Der funktionierende Code:

.include "4433def.inc"       ;Definitionsdatei einbinden, ggf. durch
                             ;2333def.inc ersetzen

 .def Laufindex   = r18
 ;.def Dunkelphase = 15000
 ;.def Hellphase   = 1000

 .macro  mdelay
  ldi  r24, low( @0 - 7 )
  ldi  r25, high( @0 - 7 )
  sbiw  r24, 3
  brcc  pc - 1
  cpi  r24, 0xFE
  brcs  pc + 3
  nop
  brne  pc + 1
.endmacro



         ldi r16, 0b00000011       ;0xFF ins Arbeitsregister r16 laden
         out DDRB, r16       ;Inhalt von r16 ins IO-Register DDRB
ausgeben

         ldi r16, 0b11111111       ;0xFF ins Arbeitsregister r16 laden
         out DDRD, r16       ;Inhalt von r16 ins IO-Register DDRB
ausgeben



Anfang:


; Funktionsprinzip: In der Phase in der z.B. die drei ersten LEDs
leuchten
; sollen die sieben restlichen mittels PCM dunkler leuchten
; Zunächst sollen eine lange Zeit lang nur die drei gewünschten LEDs
leuchten
; gelassen, anschließend für eine kurze Zeit alle LEDs.
; Dies läuft in einer Schleife 10x Rund. D.h. die drei hellen LEDs
leuchten immer
; während die anderen sieben 10x kurz aufflackern. Die Wirkung ist die
des
; gewünschten Pilotlichtes.

         ldi Laufindex, 10
      Null:
         ldi r16, 0b11111111
         out PORTD, r16
         ldi r16, 0b11111111
         out PORTB, r16
         mdelay 15000

         ldi r16, 0b00000000
         out PORTD, r16
         ldi r16, 0b11111100
         out PORTB, r16
         mdelay 500
         dec Laufindex
       brne Null

         ldi Laufindex, 10
       Eins:
         ldi r16, 0b11111110
         out PORTD, r16
         ldi r16, 0b11111111
         out PORTB, r16
         mdelay 15000

         ldi r16, 0b00000000
         out PORTD, r16
         ldi r16, 0b11111100
         out PORTB, r16
         mdelay 500
         dec Laufindex
       brne Eins

       ldi Laufindex, 10
       Zwei:
         ldi r16, 0b11111100
         out PORTD, r16
         ldi r16, 0b11111111
         out PORTB, r16
         mdelay 15000

         ldi r16, 0b00000000
         out PORTD, r16
         ldi r16, 0b11111100
         out PORTB, r16
         mdelay 500
         dec Laufindex
       brne Zwei


       ldi Laufindex, 10
       Drei:
         ldi r16, 0b11111000
         out PORTD, r16
         ldi r16, 0b11111111
         out PORTB, r16
         mdelay 15000

         ldi r16, 0b00000000
         out PORTD, r16
         ldi r16, 0b11111100
         out PORTB, r16
         mdelay 500
         dec Laufindex
       brne Drei

       ldi Laufindex, 10
       Vier:
         ldi r16, 0b11110000
         out PORTD, r16
         ldi r16, 0b11111111
         out PORTB, r16
         mdelay 15000

         ldi r16, 0b00000000
         out PORTD, r16
         ldi r16, 0b11111100
         out PORTB, r16
         mdelay 500
         dec Laufindex
       brne vier


       ldi Laufindex, 10
       Fuenf:
         ldi r16, 0b11100000
         out PORTD, r16
         ldi r16, 0b11111111
         out PORTB, r16
         mdelay 15000

         ldi r16, 0b00000000
         out PORTD, r16
         ldi r16, 0b11111100
         out PORTB, r16
         mdelay 500
         dec Laufindex
       brne Fuenf

       ldi Laufindex, 10
       Sechs:
         ldi r16, 0b11000000
         out PORTD, r16
         ldi r16, 0b11111111
         out PORTB, r16
         mdelay 15000

         ldi r16, 0b00000000
         out PORTD, r16
         ldi r16, 0b11111100
         out PORTB, r16
         mdelay 500
         dec Laufindex
       brne Sechs


       ldi Laufindex, 10
       Sieben:
         ldi r16, 0b10000000
         out PORTD, r16
         ldi r16, 0b11111111
         out PORTB, r16
         mdelay 15000

         ldi r16, 0b00000000
         out PORTD, r16
         ldi r16, 0b11111100
         out PORTB, r16
         mdelay 500
         dec Laufindex
       brne Sieben


       ldi Laufindex, 10
       Acht:
         ldi r16, 0b00000000
         out PORTD, r16
         ldi r16, 0b11111111
         out PORTB, r16
         mdelay 15000

         ldi r16, 0b00000000
         out PORTD, r16
         ldi r16, 0b11111100
         out PORTB, r16
         mdelay 500
         dec Laufindex
       brne Acht

       ldi Laufindex, 10
       Neun:
         ldi r16, 0b00000000
         out PORTD, r16
         ldi r16, 0b11111110
         out PORTB, r16
         mdelay 15000

         ldi r16, 0b00000000
         out PORTD, r16
         ldi r16, 0b11111110
         out PORTB, r16
         mdelay 500
         dec Laufindex
       brne Neun



       ldi Laufindex, 10
       Zehn:
         ldi r16, 0b00000000
         out PORTD, r16
         ldi r16, 0b11111100
         out PORTB, r16
         mdelay 15000

         ldi r16, 0b00000000
         out PORTD, r16
         ldi r16, 0b11111100
         out PORTB, r16
         mdelay 500
         dec Laufindex
       brne Zehn
Ende: rjmp Anfang


Wenn jemand Ideen hat, wie man die zehn immer wieder kehrenden
Codeabschnitte in Prozeduren giessen kann, ....

Danke schon mal im Voraus
Karl

von ...HanneS... (Gast)


Lesenswert?

Hi...

Ich habe zwar jetzt den Code nicht näher analysiert, aber was hältst du
denn davon:

- Alle Ausgabebytes in eine Tabelle (im Flash) legen
- Timer-Int auf Lauftempo einstellen (evtl. variabel)
- In ISR nächsten Wert aus Tabelle holen (LPM) und ausgeben.
- Mehrere Tabellen ergeben mehrere verschiedene Lauflichter

Oder, falls gedimmt werden muss:
- Ausgabebytes in Tabelle
- Timer-Int auf Dimmtakt
- in ISR neuen Wert holen, hochdimmen, runterdimmen...
  - also neuen Ausgabewert aus Tabelle holen
  - dann je einen Wert hochdimmen bis Maximum (255) erreicht ist
  - dann je einen Wert runterdimmen, bis Minimum (0) erreicht ist
  - dann neuen Wert aus Tabelle holen

Das Dimmer erreicht man durch Software-PWM.
- Man zählt dazu in der Timer-ISR ein Register hoch,
- legt bei 0 den Ausgabewert an den Port und erhöht/erniedrigt den
  Helligkeitswert,
- vergleicht (in jeder ISR) den PWM-Zähler mit dem Helligkeitswert
  und schaltet bei Gleichstand den Port aus
- Das Erhöhen/Vermindern des Helligkeitswertes geschieht durch
  Addition mit einem Register, das je nach Dimmrichtung 1 oder -1
  enthält. Es wird bei Erreichen von Hellmax auf -1 gesetzt, bei
  Erreichen von Hellmin auf 1, wobei jetzt der neue Ausgabewert aus
  der Tabelle geholt wird.

...HanneS...

von Karl (Gast)


Lesenswert?

Hallo Hannes,

danke für deinen Verbesserungsvorschlag. Macht wirklich einen sehr
durchdachten und universellen Eindruck.

Nur bin ich noch etwas erschlagen von den vielen Möglichkeiten, von
denen ich bisher noch nicht mal etwas geahnt habe.

Im Laufe der Zeit, bzw mit fortschreitender Programmiererfahrung
versuche ich dein System umzusetzen.

Momentan wäre ich schon froh, wenn ich obiges mit einem Prozeduraufruf
vereinfachen könnte.

Werde dazu vielleicht heute Abend noch Code hier hinein stellen, der
aber wahrscheinlich nicht funzen wird :(

Auf jeden Fall schon mal vielen Dank
Karl

von Chris (Gast)


Lesenswert?

> Das schreit nach einer Prozedur mit drei Übergabeparametern,
> aber es funktioniert leider dann nicht mehr.

Stackregister gesetzt?

von ...HanneS... (Gast)


Angehängte Dateien:

Lesenswert?

@Karl...

Ich habe mir das jetzt mal (grob) angesehen, jedoch die Bitmuster nicht
genau analysiert.

- In jeder "Abteilung" brauchst du 4 Bitmuster, die im Original
  mittels r16 in die Ports geschrieben werden. Das wird geändert!

- Nimm dafür nicht r16, sondern 4 Register, das dürfen sogar
  die ("billigen") unteren Register sein, z.B. r12...r15.

- Lege die Bitmuster in eine Tabelle, immer 4 Bytes pro "Datensatz".
  tabelle:   ;Label für Tabelle
  .db 0b00000000,0b00000001,0bxxxxxxxx,0bxxxxxxxx ;Bitmuster Satz1
  .db 0b00000001,0b00000011,0bxxxxxxxx,0bxxxxxxxx ;Bitmuster Satz2
  insgesamt 10 mal...

- Mache zum Programmbeginn eine Reset-Routine, in der du einmalig
  die Ports auf Ausgang setzt und diverse Einstellungen machst,
  auch Stackpointer

- Bau eine Hauptschleife, in der du den Z-Pointer auf den Anfang
  deiner Tabelle mit den Bitmustern setzt:

  Dann musst du noch einen Zähler einrichten, mit dessen Hilfe du
  die (abgeänderte) Ausgaberoutine 10 mal aufrufst. Danach beginnt
  die Hauptschleife von vorn (Pointer setzen).

  ldi zl,low(tabelle*2)  ;L-Byte des Z-Pointers auf Tabellenanfang
  ldi zh,high(tabelle*2) ;H-Byte auch


- Lies in der Ausgaberoutine zuerst die 4 Bytes ein

  lpm r12,z+ ;erstes Bitmuster von Tabelle holen
  lpm r13,z+ ;zweites Bitmuster von Tabelle holen
  lpm r14,z+ ;drittes Bitmuster von Tabelle holen
  lpm r15,z+ ;viertes Bitmuster von Tabelle holen

  Der Rest ist dann wie im Originalprogramm, nur dass statt r16
  die Register r12...r15 ausgegeben werden. Der Rücksprung innerhalb
  der Ausgaberoutine muss natürlich hinter den LPM-Block erfolgen!
  Sonst gibt es Kuddelmuddel mit der Tabelle.

Ich hänge mal ein Programm an, das ein ganz einfaches gedimmtes
Lauflicht an nur einem Port (8 LED's) erzeugt. Das hat zwar ein ganz
anderes Verhalten (ist eben kein "ganz besonderes Lauflicht), du
kannst aber einige meiner Vorschläge daran nachvollziehen. Es ist nur
im Simulator (AVR-Studio) getestet, nicht in Echtzeit im AVR. Dazu muss
mit Sicherheit die Wartezeit erhöht werden. Die LEDs sollten gegen GND
geschaltet werden...

Viel Erfolg...
...HanneS...

von Karl (Gast)


Lesenswert?

Hallo Hannes,

dein Code ist wirklich sehr elegant. Ich mag meinen Primiticode
gar nicht mehr anschauen ...

Sehr lehrreich. Nicht nur für mein Lauflichtproblem.

Vielen Dank
Karl

P.S. Vielleicht sollte man deinen Code als Beispiel in ein Tutorial
aufnehmen.

von ...HanneS... (Gast)


Lesenswert?

@Karl:

Das mit dem "elegant" sehe ich anders. Jedenfalls weiß ich, dass ich
verdammt Vieles nicht weiß und manches Problem nur sehr umständlich
löse. Wirklich eleganten Code schreiben einige Andere hier, teils so
elegant und optimiert, dass ich ihn (noch) nicht nachvollziehen kann.

Wo ich mir allerdings Mühe gebe, das ist das Kommentieren des Codes.
Und das mache ich nicht für Andere, sondern für mich, da ich sonst die
Übersicht verlieren würde. Denn erst die Kommentare zeigen was
eigentlich gemacht wird (und warum so und nicht anders).

Gruß... - ...HanneS...

von Karl (Gast)


Lesenswert?

@chris,

ähm, tja, wahrscheinlich meinst du das: Ich hatte den Stack nicht
initialisiert. Aber mit war auch nicht klar, dass die Rücksprungadresse
automatisch dort gespeichert wird. Ist mir erst klar geworden, als mir
beim Betrachten eines anderen Beispielcodes
dein Einwand eingefallen ist.

Mit der Stackinitialsierung klappt es auch mit Unterprogrammmaufrufen.

Aber: Wie kann ich Parameter übergeben?

Bei Makros scheint das ja ähnlich elegant zu gehen, wie in
Hochsprachen:

Folgendes Beispiel wurde mir in diesem Forum geliefert:

 .macro  mdelay
  ldi  r24, low( @0 - 7 )
  ldi  r25, high( @0 - 7 )
  sbiw  r24, 3
  brcc  pc - 1
  cpi  r24, 0xFE
  brcs  pc + 3
  nop
  brne  pc + 1
.endmacro

Ich kann dieses Makro aufrufen mit:

mdelay 1000

Wäre doch super, wenn das auch mit Unterprogrammen funzen würde.
Oder geht das sogar?

von ...HanneS... (Gast)


Lesenswert?

Hi...

Compiler für Hochsprachen legen im SRAM einen weiteren "Stack" an, zu
dessen Verwaltung ein Pointer (Doppelregister) verwendet wird. In diesen
legen sie die Parameter. Natürlich nicht mit PUSH und PULL, sondern
durch indizierte SRAM-Zugriff (ST, LD) mit Auto-In(de)crement des
Pointers.

Wenn du das nicht möchtest (kostet halt Rechenzeit, SRAM und Pointer),
dann musst du schon Register für die Parameterübergabe reservieren.
Also das Unterprogramm als Solches sehen, nicht als Funktion.

Calls zu Unterprogrammen nutze ich eigentlich nur, wenn es sich
wirklich lohnt, bei Kleinigkeiten kostet (R)CALL und RET oft mehr
Rechenzeit als das UP selbst. Das UP muss also schon von mehr als zwei
Stellen aus aufgerufen werden (oder sehr groß sein), sonst lohnt es
sich nicht.

Dass "Funktionen" (also Aufruf mit Parameterliste) in ASM das
Nonplusultra sind, mag ich nicht so recht glauben, das ist eher eine
Sache der Hochsprachen.

@Karl:
Mein (obiges) Programmbeispiel ist übrigens so strukturiert, dass die
Hauptschleife (ohne die Warteschleife) in einem Timer-Int als ISR
laufen kann. Dazu ist natürlich noch das Statusregister (SREG) zu
sichern und wiederherzustellen und je nach Timer-Int der Timer zu
behandeln (Timer.Reload bei Overflov-Int).

Ein weiteres Programm zum Analysieren findest du hier:
http://www.mikrocontroller.net/forum/read-1-113815.html#115916
Das Projekt ist zwar fragwürdig, es ist auch kein Mega8, aber die
Kommentare sind hilfreich, Dinge wie Timer und Interrupt verstehen zu
lernen, zumindest die ersten Schritte...

...HanneS...

von ...HanneS... (Gast)


Angehängte Dateien:

Lesenswert?

@Karl:

Schau dir das mal an...

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.