1.4 Ports Einlesen und Ausgeben

Schalter an PB7..PB0 einlesen und auf LEDs ausgeben

main:
  ldr r4,=GPIOB // Schalter
  ldr r5,=GPIOC // LEDs
schleife:
  ldrb r6,[r4,IDR] // Lade alle Schalter
  strb r6,[r5,ODR] // Gib auf LEDs aus
b schleife

Die Werte der Schalter PB7..PB0 sollen auf den LED7..LED0 ausgegeben werden.

Im Debug-Modus fällt auf, dass LED3 leuchtet also ist PB3 beim Debuggen 1.

Knight-Rider-Lauflicht mit Shift-Befehlen

PAP für Knight-Rider
PAP für Knight-Rider

K.I.T.T. Scanner: Ein Lauflicht, dass LED0 bis LED7 und dann wieder zurück bis LED0 leuchten lässt, also wirkt wie ein Scanner, soll erstellt werden. Links der PAP dazu.
Die Leuchtdauer ist in einer Konstanten vorgegeben:
.equ DELAYZEIT // Leuchtdauer der LED in ms
Entwickeln Sie den Assember-Code dazu und testen Sie ihn. Tipps:
warte(DELAYZEIT) mit diesem Code übersetzen:
mov r0,#DELAYZEIT
bl HAL_Delay // benutzt R2 und R3

Lösung
.equ DELAYZEIT,200 // Leuchtdauer in ms

main:
  ldr r4,=GPIOB // Schalter
  ldr r5,=GPIOC // LEDs
  mov r6,#1.    // Muster in R6
do:
  strb r6,[r5,ODR]  // Ausgeben LEDs
  mov r0,#DELAYZEIT
  bl HAL_Delay      // benutzt R2 und R3
  lsl r6,r6,#1      // 1 nach links schieben
  cmp r6,#128       // schon LED7 ?
  blt do            // Sprung, wenn kleiner
do2:
  strb r6,[r5,ODR]  // Ausgeben LEDs
  mov r0,#DELAYZEIT
  bl HAL_Delay
  lsr r6,r6,#1.     // 1 nach rechts schieben
  cmp r6,#1.        // schon LED0 ?
  bhi do2           // Sprung, wenn groesser
  b do

Lauflicht mit ausschaltbaren LEDs

PAP
Lauflicht mit ausschaltbaren LED

Einzelne LED sollen nun mit den Schaltern PB7..PB0 ausgeschaltet werden, d.h. das Ausgeben wird bei PBi=0 übersprungen.
Dazu werden am Anfang jedes Durchlaufs die Schalter in r7 eingelesen und vor der Ausgabe mit bitweisem Und verglichen ob die LED leuchten soll. Dazu dieser Code:

tst r6,r7 // bitweises und setzt Z-Flag
beq dunkel // falls Schalter PBi=0 Ausgabe überspringen

Implementieren und testen Sie die Programmerweiterung.

Lösung
.equ DELAYZEIT,200 // Geschwindigkeit einstellen

main:
  ldr r4,=GPIOB // Schalter
  ldr r5,=GPIOC // LEDs
  mov r6,#1     // Muster in R6
loop:
  ldrb r7,[r4,IDR]  // Lade Schalter PB7..PB0
do:
  tst r6,r7         // r6&r7 soll LED leuchten?
  beq dunkel        // nein dann weiter
  strb r6,[r5,ODR]  // Ausgeben LEDs
  mov r0,#DELAYZEIT
  bl HAL_Delay      // benutzt R2 und R3
dunkel:
  lsl r6,r6,#1      // 1 nach links schieben
  cmp r6,#128       // schon LED7 ?
  blt do            // Sprung, wenn kleiner
do2:
  tst r6,r7         // r6&r7 soll LED leuchten?
  beq dunkel2       // nein dann weiter
  strb r6,[r5,ODR]. // Ausgeben LEDs
  mov r0,#DELAYZEIT
  bl HAL_Delay
dunkel2:
  lsr r6,r6,#1      // 1 nach rechts schieben
  cmp r6,#1         // schon LED0 ?
  bhi do2           // Sprung, wenn groesser
  b loop
Bonus: Poti steuert über ADC die Geschwindigkeit.

In der Formelsammlung nachlesen wie die Analoge Eingabe in der Hardwareeinstellung aktiviert wird

main:
  ldr r4,=GPIOB     // Schalter
  ldr r5,=GPIOC     // LEDs
  ldr r0,=hadc		// AD-Wandler starten
  bl HAL_ADC_Start
loop:
  mov r6,#1         // Muster in R6
  ldrb r7,[r4,IDR]  // Lade Schalter
do:
  tst r6,r7         // r6&r7 soll LED leuchten?
  beq dunkel        // nein dann weiter
  strb r6,[r5,ODR]  // Ausgeben LEDs
  ldr r0,=hadc      // Wert von ad-Wandler holen
  bl HAL_ADC_GetValue // r0 <- 12 Bit Wert 0..4095
  lsr r0,r0,#3      // /8
  bl HAL_Delay      // benutzt R2 und R3
dunkel:
  lsl r6,r6,#1      // 1 nach links schieben
  cmp r6,#128       // schon LED7 ?
  blt do            // Sprung, wenn kleiner
do2:
  tst r6,r7         // r6&r7 soll LED leuchten?
  beq dunkel2       // nein dann weiter
  strb r6,[r5,ODR]  // Ausgeben LEDs
  ldr r0,=hadc      // Wert von ad-Wandler holen
  bl HAL_ADC_GetValue // r0 <- 12 Bit Wert 0..4095
  lsr r0,r0,#3      // /8
  bl HAL_Delay
dunkel2:
  lsr r6,r6,#1      // 1 nach rechts schieben
  cmp r6,#1         // schon LED0 ?
  bhi do2           // Sprung, wenn groesser
  b loop
.end

Lauflichtdurchgang mit Taster an PA1 starten

Das Lauflicht soll ruhen und ein Durchgang (hin und her) erst starten wenn Taster an PA1 gedrückt wird.
Nach der Marke loop: wird auf PA1=1 gewartet und dann ein Durchlauf ausgelöst. Verwenden Sie ein weiteres Register und den tst-Befehl dazu. Erweitern und testen Sie das Programm.

Poti-AD-Wandler steuert LED-Band

Erstellen Sie ein Programm, bei dem das Poti mit dem AD-Wandler kontinuierlich ausgelesen wird und den Wert auf den LEDs ausgibt. Beachten Sie: Der AD-Wandler hat 12-Bit Auflösung, es gibt jedoch nur 8 LED. Lösen Sie diese Teilaufgaben:

  1. Ein Hauptprogramm initialisiert die HW und ruft kontinuierlich die Ausgabe UPs auf.
  2. Ein UP leuchtpunkt verwendet Werte 0..7 in R6 keine bzw. eine der LED entsprechend leuchten.
  3. Ein UP leuchtband verwendet Werte 0..7 in R6 für eine LED-Band-Anzeige.
  4. Der Anzeige-Modus ist umschaltbar mit Schalter an PB0.
Lösung
main:
  ldr r4,=GPIOB     // Schalter
  ldr r5,=GPIOC     // LEDs
  ldr r0,=hadc.     // AD-Wandler starten
  bl HAL_ADC_Start
loop:
  ldr r0,=hadc      // Wert von ad-Wandler holen
  bl HAL_ADC_GetValue // r0 <- 12 Bit Wert 0..4095
  lsr r6,r0,#9      // /512
  ldrb r0,[r4,IDR]  // Schalter einlesen
  tst r0,#1         // PB0 geschlossen?
  bne band          // ja, dann leuchtband
  bl leuchtpunkt    // sonst leuchtpunkt
  b loop
band:
  bl leuchtband
  b loop
leuchtpunkt:
  mov r0,#1         // eine 1
  lsl r0,r0,r6      // r6 Stellen nach links verschieben
  strb r0,[r5,ODR]  // auf LED ausgeben
  bx lr             // return
leuchtband:
  mov r0,#1
bandbau:
  cmp r6,0
  beq ausgabe
  lsl r0,r0,#1
  add r0,r0,#1
  sub r6,r6,#1
  b bandbau
ausgabe:
  strb r0,[r5,ODR] // auf LED ausgeben
  bx lr            // return
.end

Leuchtband mit Tasten PA1 und PA6 steuern

Ein Leuchtband soll mit Tasten PA1 (up) und PA6 (down) gesteuert werden. Durch Betätigen von PA1 wird das Leuchtband grösser und bei PA6 kleiner.

Lösung
main:
  ldr r4,=GPIOA     // Taster
  ldr r5,=GPIOC     // LEDs
  mov r6,#0b1111	// Leuchtband
  b ausgabe
loop:
  ldrb r0,[r4,IDR]  // Taster einlesen
  tst r0,#2         // PA1 betaetigt?
  beq weiter
  cmp r6,#0xff      // alle LED schon an
  bge ausgabe       // nicht weiter
  lsl r6,r6,#1      // Up
  add r6,r6,#1
  b ausgabe
weiter:
  tst r0,#0b1000000 // PA6 betaetigt?
  beq loop
  lsr r6,r6,#1      // Down
  b ausgabe
ausgabe:
  strb r6,[r5,ODR]
  mov r0,#10        // 10 ms Prellen abwarten
  bl HAL_Delay
wartegedrueckt:
  ldrb r0,[r4,IDR]  // Taster einlesen
  tst r0,#0b1000010 // PA1 oder PA6 betaetigt?
  bne wartegedrueckt
  mov r0,#10.       // 10 ms Prellen abwarten
  bl HAL_Delay
  b loop
.end

Werteausgabe auf 7-Segmentanzeige

Schaltplan Auszug
Schaltplan Auszug
7 Segmentanzeige
7-Segmentanzeige

Der Taster an PA1 soll den Wert von r7 für einen Zähler von 0..9 erhöhen. Der Wert von r7 soll auf einer 7-Segmentanzeige an PORTC ausgegeben werden. Die Kathoden der LED-Segmente a..g sind auf einem gemeinsamen Anschluss zusammengeschaltet. Um die Anzeige leuchten zu lassen muss für die jeweilige Stelle ein Transistor Q4 (Einer-Stelle gesteuert durch PC11) bzw. Q3 (Zehner-Stelle gesteuert durch PC12) die gemeinsame Kathode auf GND schalten. PC0 – a bis PC6 – g sind für die Segmente zuständig. PC7 schaltet den Dezimalpunkt dp ein.
¡Die Pins PC11 und PC12 müssen in der IOC als Ausgänge geschaltet sein!

Füllen Sie die Tabelle mit den Ausgabe-Werten für die 7 Segmentanzeige aus.

Die Daten von Umcodierungs-Tabellen, z.B. BCD nach 7 Segment werden im Speicher typischerweise hinter dem Programm hinterlegt. Die Codes brauchen ein Byte, die Adresse der Tabelle seg7 in r6 + der Ziffernwert in r7 ergibt hier die Adresse des Ausgabecodes geladen in r0.
Im GPIOC.ODR muss noch Bit11 gesetzt werden, damit die Anzeigenstelle leuchtet.

  ldr r6,=seg7 // r6:=Adresse von Tabelle seg7
  ldr r0,[r6,r7]   // r0:=seg7[r7]
  strb r0,[r5,ODR] // r0 auf die 7seg-Anz.
  mov r0,0x800     // Bit11 in
  str r0,[r5,BSR]  // GPIOC.ODR setzen
     ....
seg7: //Codetabelle der Siebensegmentanzeige
.byte 0x3f,.. // Codewerte
BCD-WertBinHex
00b001111110x3f
1
2
3
4
5
6
7
8
9
BCD nach 7 Segment Decoder-Tabelle
Lösungsvorschlag
// 7 Segmentanzeige PC11 Einer, PC12 Zehner in IOC als Ausgang einschalten
main:
  ldr  r4,=GPIOA   // r4 verweist auf GPIOA
  ldr  r5,=GPIOC.  // r5 verweist auf GPIOC
  ldr  r6,=seg7    // r6 verweist auf die Tabelle seg7
  mov  r7,#0       // Einer
loop:              // main-loop
  ldr  r0,[r4,IDR] // GPIOA.IDR einlesen
  tst  r0,#0b10    // PA1 testen
  bne  gedrueckt   // nicht 0 dann gedrueckt
  ldr  r0,[r6,r7]  // r0:=seg7[r7]
  strb r0,[r5,ODR] // r0 auf die Siebensegmentanzeige ausgeben
  mov  r0,#0x800   // Bit11
  str  r0,[r5,BSR] // Bit11 von GPIOC.ODR setzen
  b    loop        // von vorne
gedrueckt:         // wenn PA10-Taste gedrueckt
  mov  r0,#10      // 10ms entprellen
  bl   HAL_Delay
  add  r7,#1       // um 1 hochzaehlen
nochgedrueckt:     // warten solange Taste-PA10 noch gedrueckt
  ldr  r0,[r4,IDR] // GPIOA.IDR einlesen
  tst  r0,#0b10    // PA1 testen
  bne  nochgedrueckt//bei noch gedrueckt erneut Taste abfragen
  mov  r0,#10      // 10ms entprellen
  bl   HAL_Delay
  b    loop	   //von vorne

seg7:	//Codetabelle der Siebensegmentanzeige
.byte 0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f
.end

Bonus: Zweistellige Ausgabe, Zeitmultiplexverfahren

Erweitern Sie das Programm für die Ausgabe zweier Stellen durch Zeitmultiplexen. Der Trick viele Stellen mit wenigen Leitungen ausgeben zu können beruht auf der Trägheit unserer Augen: Wenn die Stellen schnell genug kurz nacheinander aufleuchten, sieht es aus als ob sie ständig leuchten. 5ms die Einer, dann 5ms die Zehner leuchten lassen benötigt 10ms für die ganze Anzeige also 100 mal pro Sekunde, 100Hz, es flackert nicht mehr. Leitungen werden gespart: Statt 2*8 für 7 Segmente und Dezimalpunkt nur noch 8+2 für die Segmente und die jeweilige Stelle.
Erweitern Sie das Programm, hier eine mögliche “Roadmap”:

  • Ein weiteres Register R8 zählt die Zehner.
  • In der main-loop wird nach erfolgloser Tastenabfrage PA1 das Einerregister ausgegeben, 5ms ein- und dann ausschalten.
  • Dann in gleicher Weise das Zehnerregister ausgeben.
  • Wenn Tastendruck bei PA1 die Einer erhöhen und still halten bis PA1 los gelassen wird.
  • Falls die Einer den Wert 10 haben, die Zehner um 1 erhöhen und die Einer auf 0 setzen.
  • Wieder nach main-loop springen.
Lösungsvorschlag
// 7 Segmentanzeige PC11 Einer, PC12 Zehner in IOC als Ausgang einschalten
main:
  ldr  r4,=GPIOA   // r4 verweist auf GPIOA
  ldr  r5,=GPIOC   // r5 verweist auf GPIOC
  ldr  r6,=seg7    // r6 verweist auf die Tabelle seg7
  mov  r7,#0       // Einer
  mov  r8,#0       // Zehner

loop:              // main-loop
  ldr  r0,[r4,IDR] // GPIOA.IDR einlesen
  tst  r0,#0b10    // PA1 testen
  bne  gedrueckt   // nicht 0 dann gedrueckt
  // Einer ausgeben
  ldr  r0,[r6,r7]  // r0:=seg7[r7]
  strb r0,[r5,ODR] // r0 auf die Siebensegmentanzeige ausgeben
  mov  r0,Bit11    // Einer einschalten
  str  r0,[r5,BSR] // Bit11 von GPIOC.ODR
  mov  r0,#5
  bl   HAL_Delay
  mov  r0,Bit11    // Einer ausschalten
  str  r0,[r5,BRR] // Bit11 von GPIOC.ODR
  // Zehner ausgeben
  ldr  r0,[r6,r8]  // r0=seg7[r8]
  strb r0,[r5,ODR] // r0 auf die Siebensegmentanzeige ausgeben
  mov  r0,Bit12    // Zehner einschalten
  str  r0,[r5,BSR] // Bit12 von GPIOC.ODR
  mov  r0,#5
  bl   HAL_Delay
  mov  r0,Bit12    // Zehner ausschalten
  str  r0,[r5,BRR] // Bit12 von GPIOC.ODR
  b    loop        // von vorne
gedrueckt:         // wenn PA10-Taste gedrueckt
  mov  r0,#10      // 10ms entprellen
  bl   HAL_Delay
  add  r7,#1       // um 1 hochzaehlen
nochgedrueckt:     // warten solange Taste-PA10 noch gedrueckt
  ldr  r0,[r4,IDR] // GPIOA.IDR einlesen
  tst  r0,#0b10    // PA1 testen
  bne  nochgedrueckt//bei noch gedrueckt erneut Taste abfragen
  mov  r0,#10
  bl   HAL_Delay
  cmp  r7,#10     // r7==10?
  bne  loop       // nein, dann von vorne
  mov  r7,#0      // ja, dann r7 wieder bei 0 beginnen
  add  r8,#1
  b    loop       // von vorne

seg7:	//Codetabelle der Siebensegmentanzeige
.byte 0b00111111, 0b00000110, 0b01011011, 0b01001111, 0b01100110, 0b01101101, 0b01111101, 0b00000111, 0b01111111, 0b01101111
//.byte 0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f
.end

Werteausgabe auf LCD-Anzeige

main:
  bl startLCD
  bl LCD_i2c_clear
  mov R0,0
  bl LCD_i2c_cursorpos
  mov R0,#1234
  bl LCD_i2c_dezaus
  mov R0,0x40
  bl LCD_i2c_cursorpos
  ldr R0,=Hallo
  bl LCD_i2c_textaus

schleife:
  b schleife
Hallo:
.asciz "Hallo Welt"
.end

-Texte als Array abspeichern und ausgeben
-AD-Werte ausgeben