LED-Lauflicht

 

Hinweis:
Bei Ansicht auf Smartphones werden Zeilen umgebrochen, so dass die Strukturierung der s’AVR-Programme teilweise verloren geht.

LED-Lauflicht für AVR-Mikrocontroller (7.4.2016)

Wie versprochen, folgt nun eine µC-basierende LED-Applikation, die sauber strukturiert mittels s’AVR programmiert wurde (jeweils nur eine Stelle eingerückt, damit mehr Text in eine Zeile passt).

Es handelt sich um ein Lauflicht mit 6 LEDs, die zunächst nacheinander angehen und dann in derselben Laufrichtung nacheinander wieder aus und wieder an u.s.w. (wie ein kriechender Wurm) und zwar solange, bis man kurz eine Taste drückt, wodurch die Laufrichtung umgekehrt wird.

Drückt man die Taste nochmals kurz, erhält man wieder die ursprüngliche Laufrichtung, u.s.w.

Drückt man die Taste länger, so blinken die beiden äußeren LEDs solange, bis man die Taste wieder los lässt, womit erneut eine der beiden Laufrichtungen gestartet wird (je nachdem, zu welchem Zeitpunkt man die Taste losgelassen hat).

Und so einfach schaut das s’AVR-Programm aus (eine Kopie aus der Atmel-Studio-Quelldatei, Assembler-Befehle sind dank Visual Assist blau markiert und Kommentare sind grün):

 

.def LEDport = r17          ; register for LED port data (bits 0...5)

.def bitmask = r18          ; bit mask 00_0001 through 10_0000

.def LEDcnt  = r19          ; loop counter addressing the individual LEDs

 

ldi LEDport,0b0011_1111     ; set lower 6 bits ...

out DDRC,LEDport            ; ... as outputs

cbi DDRB,2                  ; Port B bit 2 = input

sbi PORTB,2                 ; enable pull-up (push button to GND)

 

LOOP                        ; main loop

  REPEAT                    ; walking LEDs from LSB to MSB

    ldi LEDport,0b0011_1111 ; set lower 6 bits ...

    out PORTC,LEDport       ; ... and all LEDs off

    ldi bitmask,0b00_0001   ; set LSB of 6 bits

    FOR LEDcnt := #12       ; 6 cycles ON one after the other

                            ; 6 cycles OFF one after the other

      in  LEDport,PORTC

      eor LEDport,bitmask   ; toggle masked LED position

      out PORTC,LEDport

      IF  LEDcnt == #7

        ldi bitmask,0b00_0001 ; reload bitmask

      ELSE

        lsl bitmask         ; next LED position

      ENDI

      rcall delay100ms      ; defines the walking speed

      EXITIF !%PINB,2       ; push button LOW?

    ENDF

  UNTIL !%PINB,2            ; push button still LOW?

 

 ; pushing the button continuously will flash the MSB and LSB LEDs

 ; and continue one of the modes after releasing the button

 

  REPEAT                    ; walking LEDs from MSB (reverse order)

    ldi LEDport,0b0011_1111 ; set lower 6 bits ...

    out PORTC,LEDport       ; ... and all LEDs off

    ldi bitmask,0b10_0000   ; set MSB of 6 bits

    FOR LEDcnt := #12       ; 6 cycles ON one after the other

                            ; 6 cycles OFF one after the other

      in  LEDport,PORTC

      eor LEDport,bitmask   ; toggle masked LED position

      out PORTC,LEDport

      IF  LEDcnt == #7

        ldi bitmask,0b10_0000 ; reload bitmask

      ELSE

        lsr bitmask         ; next LED position

      ENDI

      rcall delay100ms      ; defines the walking speed

      EXITIF !%PINB,2       ; push button LOW?

    ENDF

  UNTIL !%PINB,2            ; push button still LOW?

ENDL                        ; main loop (forever)


So einfach kann strukturierte AVR-Assembler-Programmierung ausschauen!

Genau genommen wurde sogar nur die erste REPEAT/UNTIL-Struktur getippt, denn die zweite ist bis auf die geänderte Bitmaske und die Schieberichtung (Unterschiede rot markiert) exakt dieselbe.

Natürlich ist das unstrukturierte "flache" Assembler-Programm nach der Compilierung durch s’AVR einiges länger[1] und alles andere als übersichtlich.

Das "flache" AVR-Assembler-Programm (21.1.2018)

Falls jemand das Lauflicht selbst in purem AVR-Assembler nachprogrammieren will, folgt hier noch das von s’AVR 2.x im effizienten Mode erzeugte "flache" AVR-Assembler-Programm (Achtung: s’AVR-Befehle einschließlich dort stehender Kommentare sind hier unterdrückt, der von s’AVR erzeugte Code wurde rot markiert - eine ganze Menge):

.def LEDport = r17        ; register for LED port data (bits 0...5)

.def bitmask = r18        ; bit mask 00_0001 through 10_0000

.def LEDcnt = r19         ; loop counter addressing the individual LEDs

 

ldi LEDport,0b0011_1111   ; set lower 6 bits ...

out DDRC,LEDport          ; ... as outputs

cbi DDRB,2                ; Port B bit 2 = input (/SS, default)

sbi PORTB,2               ; enable pull-up (push button to GND)

 

_L1:

_L4:

  ldi LEDport,0b0011_1111 ; set lower 6 bits

  out PORTC,LEDport       ; ...and all LEDs off

  ldi bitmask,0b00_0001   ; set LSB of 6 bits

  LDI LEDcnt,12

_L7:

                          ; 6 cycles OFF one after the other

  in LEDport,PORTC

  eor LEDport,bitmask     ; toggle masked LED position

  out PORTC,LEDport

  CPI LEDcnt,7

  BRNE _L10

  ldi bitmask,0b00_0001   ; reload bitmask

  RJMP _L12

_L11:

_L10:

  lsl bitmask             ; next LED position

_L12:

  rcall delay100ms        ; defines the walking speed

  SBIS PINB,2

  RJMP _L9

  DEC LEDcnt

  BRNE _L7

_L9:

  SBIC PINB,2

  RJMP _L4

 

 ; pushing the button continuously will flash the MSB and LSB LEDs

 ; and continue one of the modes after releasing the button

 

_L14:

  ldi LEDport,0b0011_1111 ; set lower 6 bits

  out PORTC,LEDport       ; ...and all LEDs off

  ldi bitmask,0b10_0000   ; set MSB of 6 bits

  LDI LEDcnt,12

_L17:

                          ; 6 cycles OFF one after the other

  in LEDport,PORTC

  eor LEDport,bitmask     ; toggle masked LED position

  out PORTC,LEDport

  CPI LEDcnt,7

  BRNE _L20

  ldi bitmask,0b0010_0000 ; reload bitmask

  RJMP _L22

_L21:

_L20:

  lsr bitmask             ; next LED position

_L22:

  rcall delay100ms        ; defines the walking speed

  SBIS PINB,2

  RJMP _L19

  DEC LEDcnt

  BRNE _L17

_L19:

  SBIC PINB,2

  RJMP _L14

  RJMP _L1
 

Einzig die Interrupt-Sprungtabelle, die Initialisierung des Stack-Pointers und das aufgerufene Unterprogramm delay100ms (eine 100ms-Warteschleife, ebenfalls mittels s’AVR erstellt) sind weder im s’AVR-Programm noch im "flachen" AVR-Assembler-Programm aufgelistet, auch nicht die Include-Zeile für den jeweiligen AVR-Mikrocontroller.

Im vorliegenden Fall ist es ein ATmega328P (include "m328pdef.inc"[2]), mit dem eine Schaltung aufgebaut und das Programm auch gestestet wurde.

Die LEDs sind quasi mit gemeinsamer Anode an +5V angeschlossen und gehen per LED-Vorwiderstand direkt an die AVR-Port-Ausgänge.

Dieses LED-Lauflicht-Beispiel wurde gewählt, da die Funktion bereits etwas komplexer ist und das s’AVR-Programm bis auf WHILE/ENDW beinahe alle s’AVR-Anweisungen enthält.

Trickreich - und sauber strukturiert - ist die Abfrage der Taste mittels EXITIF, wodurch zunächst die FOR-Schleife verlassen und über die UNTIL-Abfrage auf denselben Pin-Zustand an die REPEAT/UNTIL-Struktur für die jeweils andere Laufrichtung weitergereicht wird.
Das Entprellen der Taste geschieht automatisch über die 100ms-Verzögerungsschleifen.

Natürlich kann man dieses Programmbeispiel einfach an seine eigenen Wünsche anpassen, z.B. für mehr LEDs und ein anderes zeitliches Verhalten.

Per Hardware

Wollte man dieselbe Lauflicht-Funktion ausschließlich per Hardware (also ohne µC) realisieren, würde man etwa zwei bis fünf ICs benötigen (Schieberegister, Taktgeber und ein paar Gatter). Das wäre also schon etwas mehr Platz- und Lötaufwand und auch deutlich weniger flexibel.

Allerdings benötigt man dafür keine µC-Entwicklungsumgebung und man muss auch kein Programm schreiben.

nach oben


[1] Im obigen s’AVR-Programm sind 18 s’AVR-Anweisungen und 22 reine Assembler-Befehle enthalten. Im übersetzten Assembler-Programm werden daraus 47 reine Assembler-Befehle im "schmerzfreien" Mode und 43 reine Assembler-Befehle im "effizienten" Mode (s’AVR 2.x) - und eine ganze Menge Labels.

[2] Bei einem aktuellen Atmel-Studio ist diese Include-Zeile nicht nötig, da der Ziel-µC separat im jeweiligen Projekt ausgewählt wird. Diese Zeile schadet aber auch nicht, solange sie nicht mit dem Ziel-µC des Projekts kollidiert.