(→Bascom ISR in Assembler programmieren) |
|||
(9 dazwischenliegende Versionen desselben Benutzers werden nicht angezeigt) | |||
Zeile 16: | Zeile 16: | ||
== Beispiel 1: Entprellung einer 3 Tasten Uhr in Bascom == | == Beispiel 1: Entprellung einer 3 Tasten Uhr in Bascom == | ||
− | Der nachfolgende Code verwendet kein Assembler. Es wurde zur Entprellung der Tasten der Bascom Befehl DEBONCE verwendet. | + | Der nachfolgende Code verwendet noch kein Assembler und dient als Vorlage für Beispiel 2. |
+ | |||
+ | Es wurde zur Entprellung der Tasten der Bascom Befehl DEBONCE verwendet. Der Code ist bewusst simpel gestrickt. Trotzdem wurden bewusst mächtige Bascom Befehle wie "Lcd Date$ ; " " ; Time$" verwendet, um die Stärke vom Bascom gegenüber ASM herauszustellen. | ||
+ | |||
+ | Die Uhr wird über 3 Tasten (Plus, Minus, Enter) gestellt. Jeder kennt das... | ||
+ | Beim Stellen laufen die Werte in beide Richtungen über (Beispiel Stunde : ...23,0,1....23,0,1..) | ||
+ | |||
<pre> | <pre> | ||
$regfile = "m8def.dat" | $regfile = "m8def.dat" | ||
Zeile 41: | Zeile 47: | ||
'********* input key and debounce *************************************************** | '********* input key and debounce *************************************************** | ||
− | 'Zuordnung der Tasten zu den Pins | + | 'Zuordnung der Tasten zu den Pins |
Const Key_plus = &B0000_1000 '1=BUTTON_INPUT | Const Key_plus = &B0000_1000 '1=BUTTON_INPUT | ||
Const Key_minus = &B0001_0000 | Const Key_minus = &B0001_0000 | ||
Zeile 119: | Zeile 125: | ||
Data " " , "Sec " , "Min " , "Hour" , "Day " , "Mon " , "Year" | Data " " , "Sec " , "Min " , "Hour" , "Day " , "Mon " , "Year" | ||
</pre> | </pre> | ||
− | Der Code wurde bereits auf Länge optimiert (im Rahmen einer geboteten Übersichtlichkeit des Quelltextes). | + | Der Code wurde bereits etwas auf Länge optimiert (im Rahmen einer geboteten Übersichtlichkeit des Quelltextes). |
Getestet mit Bascom Version BASCOM 1.11.9.1 [Codelänge 1376 Byte] | Getestet mit Bascom Version BASCOM 1.11.9.1 [Codelänge 1376 Byte] | ||
Trotzdem besitzt das Beispiel einige Schwächen: | Trotzdem besitzt das Beispiel einige Schwächen: | ||
− | * | + | * der Befehl DEBONCE muss ständig gepollt werden, d.h. '''der AVR ist zu 100% beschäftigt (Batteriebetrieb!''') |
− | * wird eine Taste gedrückt, friert der AVR für 30ms ein (andere Unterprogramme können | + | * wird eine Taste gedrückt, '''friert der AVR für 30ms ein''' (andere Unterprogramme können in der Zeit nicht angesprungen werden) |
− | * Debounce | + | * der Bascom Befehl Debounce kann '''kein Autorepeat''', d.h. langes Drücken einer Taste bewirkt keine zusätzlichen Impulse |
− | + | ||
== Beispiel 2: Entprellung einer 3 Tasten Uhr in einer Bascom-ISR mit Assembler-Unterstützung == | == Beispiel 2: Entprellung einer 3 Tasten Uhr in einer Bascom-ISR mit Assembler-Unterstützung == | ||
Zeile 140: | Zeile 145: | ||
Der Code beseitigt die Schwächen des Bascom Befehls DEBOUNCE: | Der Code beseitigt die Schwächen des Bascom Befehls DEBOUNCE: | ||
− | * der AVR | + | * der AVR verbringt über '''99,9% der Zeit im Powersave-Modus''' (nur wenige µA Stromverbrauch) |
* wird eine Taste gedrückt, friert der AVR nicht ein | * wird eine Taste gedrückt, friert der AVR nicht ein | ||
− | * Debounce besitzt jetzt ein Autorepeat, d.h. langes drücken einer Taste bewirkt nach ca. 1sec eine Impulsfolge von 8Hz (beliebig einstellbar) | + | * Debounce besitzt jetzt ein '''Autorepeat''', d.h. langes drücken einer Taste bewirkt nach ca. 1sec eine Impulsfolge von 8Hz (beliebig einstellbar) |
+ | * kein zusätzlicher Timer erforderlich - das Unterprogramm Debounce_port wird '''an einem vorhandenen Timer rangehangen''' | ||
+ | * Bis zu '''8 Tasten können gleichzeitig (parallel) abgearbeitet''' werden. Diese müssen aber am gleichen Port hängen. Falls das nicht möglich ist, muss das UP Debounce_port geringfügig erweitert werden, so dass am Anfang bis zu 8 Pins gesammelt und in einem Register zusammengeführt werden. | ||
Die Entprellzeit beträgt übrigens im Beispiel wie bei Bascom ca. 30ms. | Die Entprellzeit beträgt übrigens im Beispiel wie bei Bascom ca. 30ms. | ||
Zeile 340: | Zeile 347: | ||
' Key_rep Wiederholungszähler für Autorepeat | ' Key_rep Wiederholungszähler für Autorepeat | ||
' Key_ct0 / Key_ct1 2-Bit-Zähler für Entprellung | ' Key_ct0 / Key_ct1 2-Bit-Zähler für Entprellung | ||
− | 'Result: Key_press: wechselt zu 1 bei Erkennung einer gedrückten Taste | + | 'Result: Key_press: Bit des Pins wechselt zu 1 bei Erkennung einer gedrückten Taste |
' Status von Key_press in Main nach Auslesen zurücksetzen! | ' Status von Key_press in Main nach Auslesen zurücksetzen! | ||
'Thanks to Peter Dannegger | 'Thanks to Peter Dannegger | ||
Zeile 437: | Zeile 444: | ||
andi r16,128-1 'if _tick128 is not an 128 multiple | andi r16,128-1 'if _tick128 is not an 128 multiple | ||
</pre> | </pre> | ||
+ | |||
+ | ==Warum eine ISR in Assembler programmieren?== | ||
+ | Ein gutes AVR-Programm hängt nicht irgendwo im Code fest. | ||
+ | Insbesonders sollten ISR sehr schnell durchlaufen werden, um andere Prozesse nicht zum Stocken zu bringen. (Infrarotsensor auslesen etc). | ||
+ | |||
+ | Stehen nach einem Interrupt größere Sachen an (Print, LCD etc), dann setzt man in der ISR nur ein Flag und bearbeitet das Problem in der Main. Im Beispiel 2 wurde das Flag flagRTC_Changed gesetzt, um jede Sekunde das LCD zu aktualisieren. In der Variablen Key_press wird nach jedem Interrupt-Ereignis (Aufwachen aus Powersave) nachgesehen, ob eine neue Taste erkannt wurde. | ||
+ | |||
+ | Wird in Bascom eine ISR ohne dem Zusatz NOSAVE programmiert, sichert Bascom fast alle Register auf den Stack. Das kostet Zeit und führt zu Stacküberläufen, wenn man den Stackbereich nicht deutlich anhebt. Hier ein Bascom-Code Schnipsel einer solchen ISR. | ||
+ | <pre> | ||
+ | PUSH R0 Push register on stack | ||
+ | PUSH R1 | ||
+ | PUSH R2 | ||
+ | PUSH R3 | ||
+ | PUSH R4 | ||
+ | PUSH R5 | ||
+ | PUSH R7 | ||
+ | PUSH R10 | ||
+ | PUSH R11 | ||
+ | PUSH R16 | ||
+ | PUSH R17 | ||
+ | PUSH R18 | ||
+ | PUSH R19 | ||
+ | PUSH R20 | ||
+ | PUSH R21 | ||
+ | PUSH R22 | ||
+ | PUSH R23 | ||
+ | PUSH R24 | ||
+ | PUSH R25 | ||
+ | PUSH R26 | ||
+ | PUSH R27 | ||
+ | PUSH R28 | ||
+ | PUSH R29 | ||
+ | PUSH R30 | ||
+ | PUSH R31 | ||
+ | |||
+ | IN R24,SREG In from I/O location | ||
+ | PUSH R24 Push register on stack | ||
+ | |||
+ | 'eigener Code in der ISR | ||
+ | |||
+ | POP R24 Pop register from stack | ||
+ | OUT SREG,R24 Out to I/O location | ||
+ | |||
+ | POP R31 Pop register from stack | ||
+ | POP R30 | ||
+ | POP R29 | ||
+ | POP R28 | ||
+ | POP R27 | ||
+ | POP R26 | ||
+ | POP R25 | ||
+ | POP R24 | ||
+ | POP R23 | ||
+ | POP R22 | ||
+ | POP R21 | ||
+ | POP R20 | ||
+ | POP R19 | ||
+ | POP R18 | ||
+ | POP R17 | ||
+ | POP R16 | ||
+ | POP R11 | ||
+ | POP R10 | ||
+ | POP R7 | ||
+ | POP R5 | ||
+ | POP R4 | ||
+ | POP R3 | ||
+ | POP R2 | ||
+ | POP R1 | ||
+ | POP R0 | ||
+ | RETI | ||
+ | </pre> | ||
+ | |||
+ | Das sind 26 zusätzliche Byte auf dem Stack. Unser Beispiel 2 benötigt nur 4 Register und SREG, d.h. 5 Byte auf dem Stack. | ||
==Siehe auch== | ==Siehe auch== | ||
Zeile 447: | Zeile 526: | ||
* [http://www.mikrocontroller.net/articles/AVR-Tutorial:_Tasten] - AVR-Tutorial: Tasten | * [http://www.mikrocontroller.net/articles/AVR-Tutorial:_Tasten] - AVR-Tutorial: Tasten | ||
* [http://www.mikrocontroller.net/articles/Entprellung] - Entprellung | * [http://www.mikrocontroller.net/articles/Entprellung] - Entprellung | ||
− | * [http://www.mikrocontroller.net/topic/6492] - Tasten entprellen - Bulletproof | + | * [http://www.mikrocontroller.net/topic/6492] - Tasten entprellen - Bulletproof |
+ | |||
+ | [[Kategorie:Software]] | ||
+ | [[Kategorie:Quellcode Bascom]] |
Aktuelle Version vom 23. Dezember 2008, 20:00 Uhr
Inhaltsverzeichnis
Die Hardware für die Code-Beispiele: Bascom ISR in Assembler programmieren
Da immer wieder Fragen zur Assembler-ISR-Programmierung in Bascom auftreten, will ich hier an einem einfachen Beispiel einer 3 Tasten Uhr mit LCD Display den Weg aufzeigen.
Die Hardware
Die Schaltung ist auf einem Steckbrett schnell gesteckt. Der zusätzliche Max232 dient nur der einfachen Bootloader-Programmierung.
zur Hilfestellung für Einsteiger noch der Schaltplan:
Der u.g. Beispiel-Code lässt sich auf alle AVR-Starterplatinen mit Tasten+LCD schnell anpassen.
Beispiel 1: Entprellung einer 3 Tasten Uhr in Bascom
Der nachfolgende Code verwendet noch kein Assembler und dient als Vorlage für Beispiel 2.
Es wurde zur Entprellung der Tasten der Bascom Befehl DEBONCE verwendet. Der Code ist bewusst simpel gestrickt. Trotzdem wurden bewusst mächtige Bascom Befehle wie "Lcd Date$ ; " " ; Time$" verwendet, um die Stärke vom Bascom gegenüber ASM herauszustellen.
Die Uhr wird über 3 Tasten (Plus, Minus, Enter) gestellt. Jeder kennt das... Beim Stellen laufen die Werte in beide Richtungen über (Beispiel Stunde : ...23,0,1....23,0,1..)
$regfile = "m8def.dat" $framesize = 32 $swstack = 32 $hwstack = 32 $crystal = 8000000 '********* LCD *************************************************** 'Pins des LCD-Modules setzen ggf. an eigene Anschlüsse anpassen Config Lcdpin = Pin , Db4 = Portc.2 , Db5 = Portc.3 , Db6 = Portc.4 , Db7 = Portc.5 , E = Portc.1 , Rs = Portc.0 Config Lcdmode = Port Config Lcdbus = 4 '4 bit mode Config Lcd = 20 * 4 Initlcd Cursor Off '********* ISR Timer 2 *************************************************** Config Clock = Soft Enable Interrupts Config Date = Dmy , Separator = / _sec = 00 : _min = 15 : _hour = 14 : _day = 30 : _month = 05 : _year = 08 '********* input key and debounce *************************************************** 'Zuordnung der Tasten zu den Pins Const Key_plus = &B0000_1000 '1=BUTTON_INPUT Const Key_minus = &B0001_0000 Const Key_enter = &B0010_0000 Const Pinb_mask = Key_plus + Key_minus + Key_enter 'Maske zum Filtern der Eingänge Const Pinb_mask_compl = &HFF - Pinb_mask 'Pinb_mask compliment 'hier sind die Tasten am PortB Ddrb = Ddrb And Pinb_mask_compl 'DDRB: 0=INPUT 1=OUTPUT Portb = Portb Or Pinb_mask '1=activate internal pull-up resistors for inputs '********* allgemeines *************************************************** Dim I As Byte , J As Byte , K As Byte , L As Byte Dim Setdatetime_index As Byte Setdatetime_index = 1 'first index of date_array is 1 Dim Date_array(6) As Byte At _sec Overlay 'array for _sec/_min/_hour/_day/_month/_year '********* main loop *************************************************** Do Home 'cursor home Lcd Date$ ; " " ; Time$ 'show the date and time Locate 2 , 1 Lcd "Set: " ; Lookupstr(setdatetime_index , Data_dateset) Debounce Pinb.3 , 0 , Key_plus_pressed , Sub Debounce Pinb.4 , 0 , Key_minus_pressed , Sub Debounce Pinb.5 , 0 , Key_enter_pressed , Sub Loop '********* Gusub *************************************************** Key_plus_pressed: Gosub Date_values If J < L Then Incr J Else J = K Date_array(setdatetime_index) = J Return Key_minus_pressed: Gosub Date_values If J > K Then Decr J Else J = L Date_array(setdatetime_index) = J Return Key_enter_pressed: If Setdatetime_index < 6 Then Incr Setdatetime_index Else Setdatetime_index = 1 Return '--------------------------------------------------------------- 'Subroutine: Date_values 'Call from: main 'Purpose: Adjusts the Date and Clock 'Parameters: key input '--------------------------------------------------------------- Date_values: L = Lookup(setdatetime_index , Date_max) If Setdatetime_index = 4 Then L = Lookup(_month , Date_month) K = _year And 3 If _month = 2 Then If K = 0 Then Incr L End If K = Lookup(Setdatetime_index , Date_min) J = Date_array(setdatetime_index) Return '******** Select Date and Time ************************************ Date_min: Data 0 , 0 , 0 , 0 , 1 , 1 , 0 Date_max: Data 0 , 59 , 59 , 23 , 31 , 12 , 99 Date_month: Data 0 , 31 , 28 , 31 , 30 , 31 , 30 , 31 , 31 , 30 , 31 , 30 , 31 Data_dateset: Data " " , "Sec " , "Min " , "Hour" , "Day " , "Mon " , "Year"
Der Code wurde bereits etwas auf Länge optimiert (im Rahmen einer geboteten Übersichtlichkeit des Quelltextes). Getestet mit Bascom Version BASCOM 1.11.9.1 [Codelänge 1376 Byte]
Trotzdem besitzt das Beispiel einige Schwächen:
- der Befehl DEBONCE muss ständig gepollt werden, d.h. der AVR ist zu 100% beschäftigt (Batteriebetrieb!)
- wird eine Taste gedrückt, friert der AVR für 30ms ein (andere Unterprogramme können in der Zeit nicht angesprungen werden)
- der Bascom Befehl Debounce kann kein Autorepeat, d.h. langes Drücken einer Taste bewirkt keine zusätzlichen Impulse
Beispiel 2: Entprellung einer 3 Tasten Uhr in einer Bascom-ISR mit Assembler-Unterstützung
Die Schwächen des Beispiel 1 sollen ausgebügelt werden. Die Programmstruktur (3 Tasten Uhr) wird zum direkten Vergleich beibehalten.
Dazu wird der Bascom Befehl Debounce in Assembler nachgebaut. Um den AVR zu entlasten wird die Debounce Routine in die vorhanden Timer2-ISR gehangen. Da im Beispiel 1 Timer2 asynchron mit externem 32kHz-Quarz gefahren wird, ist dieser Timer2 bereits von der Bascom Softclock (Takt 1Hz) belegt.
Im Beispiel wird gezeigt, wie man sich an die Softclock heranhängen kann. Gleichzeitig wird der Takt der Timer2-ISR auf 128Hz erhöht, um die Tasten zu pollen.
Zum Entprellen wurde der bekannte Code von Peter Dannegger (Tasten entprellen - Bulletproof) angepasst. Der techn. Hintergrund wird in dem ausführlichen Artikel http://www.mikrocontroller.net/articles/AVR-Tutorial:_Tasten erläutert.
Der Code beseitigt die Schwächen des Bascom Befehls DEBOUNCE:
- der AVR verbringt über 99,9% der Zeit im Powersave-Modus (nur wenige µA Stromverbrauch)
- wird eine Taste gedrückt, friert der AVR nicht ein
- Debounce besitzt jetzt ein Autorepeat, d.h. langes drücken einer Taste bewirkt nach ca. 1sec eine Impulsfolge von 8Hz (beliebig einstellbar)
- kein zusätzlicher Timer erforderlich - das Unterprogramm Debounce_port wird an einem vorhandenen Timer rangehangen
- Bis zu 8 Tasten können gleichzeitig (parallel) abgearbeitet werden. Diese müssen aber am gleichen Port hängen. Falls das nicht möglich ist, muss das UP Debounce_port geringfügig erweitert werden, so dass am Anfang bis zu 8 Pins gesammelt und in einem Register zusammengeführt werden.
Die Entprellzeit beträgt übrigens im Beispiel wie bei Bascom ca. 30ms.
Der Code ist umfangreich kommentiert.
$regfile = "m8def.dat" $framesize = 32 $swstack = 32 $hwstack = 32 $crystal = 8000000 Config Portb.1 = Output 'Ein Pin wird als LED-Ausgang konfiguriert '********* LCD *************************************************** 'Pins des LCD-Modules setzen ggf. an eigene Anschlüsse anpassen Config Lcdpin = Pin , Db4 = Portc.2 , Db5 = Portc.3 , Db6 = Portc.4 , Db7 = Portc.5 , E = Portc.1 , Rs = Portc.0 Config Lcdmode = Port Config Lcdbus = 4 '4 bit mode Config Lcd = 20 * 4 Initlcd Cursor Off '********* ISR Timer 2 *************************************************** Config Clock = User 'you can debounce with 128Hz (max. Verzögerung 4/128=0,031s) Config Timer2 = Timer , Async = On , Prescale = 1 'Prescaler 8->16Hz or 1->128Hz On Ovf2 _isr_t2ovf Nosave Enable Ovf2 Enable Interrupts Config Date = Dmy , Separator = / _sec = 00 : _min = 15 : _hour = 14 : _day = 30 : _month = 05 : _year = 08 Dim Flagrtc_changed As Bit Dim Tick_128hz As Byte '********* input key and debounce *************************************************** 'Zuordnung der Tasten zu den Pins - diese müssen alle am selben Port hängen! Const Key_plus = &B0000_1000 '1=BUTTON_INPUT Const Key_minus = &B0001_0000 Const Key_enter = &B0010_0000 Const Pinb_mask = Key_plus + Key_minus + Key_enter 'Maske zum Filtern der Eingänge Const Pinb_mask_compl = &HFF - Pinb_mask 'Pinb_mask compliment 'hier sind die Tasten am PortB Ddrb = Ddrb And Pinb_mask_compl 'DDRB: 0=INPUT 1=OUTPUT Portb = Portb Or Pinb_mask '1=activate internal pull-up resistors for inputs Key_port Alias Pinb Dim Key_state As Byte '0/1 invertierter Status der Tasten (1=gedrückt) Dim Key_press As Byte 'Trigger für gedrückte Taste Dim Key_rep As Byte 'Wiederholungszähler für Autorepeat Dim Key_ct0 As Byte , Key_ct1 As Byte '2 Bit Zähler für Entprellung Const Key_repeat_start = 127 'Anzahl-1 der Timer-ISR bis Autorepeat beginnt (ca. 500-1000ms) Const Key_repeat_next = 15 'Anzahl-1 der Timer-ISR für Autorepeat Wiederholrate (ca. 125ms) 'debounce initial values Key_ct0 = 255 'Programmstart bei gedrückten Tasten abfangen Key_ct1 = 255 'Key_rep = Key_repeat_start 'Paranoia '********* allgemeines *************************************************** Dim I As Byte , J As Byte , K As Byte , L As Byte Dim Setdatetime_index As Byte Setdatetime_index = 2 'first index of date_array is 1, 2=_min Dim Date_array(6) As Byte At _sec Overlay 'array for _sec/_min/_hour/_day/_month/_year '********* main loop *************************************************** Do Home 'cursor home Lcd Date$ ; " " ; Time$ 'show the date and time Locate 2 , 1 Lcd "Set: " ; Lookupstr(setdatetime_index , Data_dateset) 'zum Stromsparen wird die 128Hz-Schleife in ASM geschieben: Flagrtc_changed = 0 Enable Interrupts 'Paranoia 'Do Powersave Loop Until ((Flagrtc_changed = 1) OR (Key_press>0)) Main_1: Powersave 'Timer 2 läuft weiter lds r16,{Key_press} 'Taste gedrückt? TST r16 BRNE main_2 lds r16,{flagRTC_Changed} 'eine neue Sekunde? sbrS r16,bit.flagRTC_Changed RJMP Main_1 Main_2: If Key_press > 0 Then Gosub Setdatetime_input Loop '--------------------------------------------------------------- 'Subroutine: Setdatetime_input 'Call from: main 'Purpose: Set the Date and Clock 'Parameters: key input '--------------------------------------------------------------- Setdatetime_input: Disable Interrupts 'ISR sperren I = Key_press 'key auslesen Key_press = 0 L = Lookup(setdatetime_index , Date_max) If Setdatetime_index = 4 Then L = Lookup(_month , Date_month) K = _year And 3 If _month = 2 Then If K = 0 Then Incr L End If K = Lookup(setdatetime_index , Date_min) J = Date_array(setdatetime_index) If I = Key_plus Then If J < L Then Incr J Else J = K End If If I = Key_minus Then If J > K Then Decr J Else J = L End If Date_array(setdatetime_index) = J Enable Interrupts 'ISR freigeben If I = Key_enter Then If Setdatetime_index < 6 Then Incr Setdatetime_index Else Setdatetime_index = 1 End If Return '********* ISR Timer 2 *************************************************** '--------------------------------------------------------------- 'Subroutine: _isr_t2ovf 'Call from: Timer2 (128Hz) 'Purpose: RTC, Debounce 'Result: RTC: _sec, _min, _hour, _day, _month, _year ' Debounce: siehe unten '--------------------------------------------------------------- $external _soft_clock Const _sectic = 0 'Compilerstatement for CONFIG CLOCK = USER , GOSUB <> SECTIC _isr_t2ovf: $asm push r16 in r16,sreg push r16 'save R16 and SREG in stack ' debounce key push r17 'now save all used registers push r18 PUSH r19 !Call Debounce_port POP r19 'restore registers pop r18 pop r17 lds r16,{tick_128hz} 'increment _tick128 every 1/128s inc r16 sts {tick_128hz},r16 andi r16,128-1 'if _tick128 is not an 128 multiple brne _T2OVF_END ' set_digit exit isr, else a second has passed ' this bit variable will be set up every new second lds r16,{flagRTC_Changed} 'bit variable sbr r16,2^bit.flagRTC_Changed sts {flagRTC_Changed},r16 'it's the main code duty to reset the flag after noticing it ' go to internal Bascom ISR-Routine: Softclock pop r16 'get content of SREG !out sreg,r16 'restore sreg pop r16 JMP _SOFT_CLOCK 'original RETI _t2ovf_end: pop r16 'get content of SREG !out sreg,r16 'restore sreg pop r16 $end Asm Return 'and exit isr with RETI '********* Debounce *************************************************** '--------------------------------------------------------------- 'Subroutine: Debounce_port ' gleichzeitige Erkennnung von max. 8 gedrückten Tasten mit Entprellung und Autorepeat ' jede Taste wird für einen Statuswechsel 3x abgetastet 'verwendete Register: R16, R17, R18, R19 'Variablen: Key_state Status der Tasten 0/1 ' Key_rep Wiederholungszähler für Autorepeat ' Key_ct0 / Key_ct1 2-Bit-Zähler für Entprellung 'Result: Key_press: Bit des Pins wechselt zu 1 bei Erkennung einer gedrückten Taste ' Status von Key_press in Main nach Auslesen zurücksetzen! 'Thanks to Peter Dannegger '--------------------------------------------------------------- Debounce_port: 'ca. 108 Byte $asm in R18, key_port com R18 'pin low is active ANDI R18, Pinb_mask 'filter pins with Pinb_mask LDS R19, {Key_state} eor R18, R19 'detect level change at pin input LDS R16, {Key_ct0} 'the 8 channels 2-Bit-Counter LDS R17, {Key_ct1} and R16, R18 'reset the 2-bit-counter in R17:R16 and R17, R18 com R16 'decrement R17:R16 eor R17, R16 STS {Key_ct0} , R16 'save counter STS {Key_ct1} , R17 and R18, R16 'IF counter = &B11 set_digit input debounced and R18, R17 eor R19, R18 '0<->1 toggle state STS {Key_state} , R19 LDS R16, {key_press} and R18, R19 or R16, R18 '0->1 key press detected LDS R17, {Key_rep} tst R19 'Key_state: irgendeine Taste gedrückt? breq Key_isr_rep dec R17 brpl Key_isr_finish 'IF Key_rep<0 set_digit Wartezeit abgelaufen mov R16, R19 'autorepeat key_press = key_state ldi R17, key_repeat_next 'set autorepeat repeat timer rjmp Key_isr_finish Key_isr_rep: ldi R17, key_repeat_start 'reset autorepeat start timer Key_isr_finish: STS {Key_rep} , R17 'save the autorepeat timer STS {key_press} , R16 'save debounced key $end Asm Return '********* RTC *************************************************** 'the following void routines are needed if you want to use ' the time/date bascom functions. You may fill them as you need Getdatetime: Return Settime: Return Setdate: Return End '******** Select Date and Time ************************************ Date_min: Data 0 , 0 , 0 , 0 , 1 , 1 , 0 Date_max: Data 0 , 59 , 59 , 23 , 31 , 12 , 99 Date_month: Data 0 , 31 , 28 , 31 , 30 , 31 , 30 , 31 , 31 , 30 , 31 , 30 , 31 Data_dateset: Data " " , "Sec " , "Min " , "Hour" , "Day " , "Mon " , "Year"
Getestet mit Bascom Version BASCOM 1.11.9.1 [Codelänge 1482 Byte]
Der geringe Codezuwachs von 106 Byte ist der erweiterten Funktionalität geschuldet. Der Code ließe sich durch weitere Anwendung von Assembler problemlos einkürzen, jedoch soll hier ein übersichtlicher der Weg zum Einstieg aufgezeigt werden.
Die Tasten werden im Beispiel mit 128Hz abgetastet. Eine Taste muss 3x in Folge den gleichen Pegelk haben, um als Zustandswechsel anerkannt zu werden. (Verzögerung ca. 30ms).
Wer es etwas ruhiger mag, kann wegen der vorgegebenen Prescaler von Timer2 (1,8,32,64 etc) erst 16 Hz als nächsten Takt zum Pollen wählen. Das ergibt unangenehme Verzögerungen beim Tastendruck von ca. 0,2s.
Andere Frequenzen erreicht man aber durch den CTC Mode. Der wird von Bascom nicht unterstützt, weshalb man die Register von Hand setzen muss.
'you can debounce with 128Hz (max. Verzögerung 4/128=0,031s) Config Timer2 = Timer , Async = On , Prescale = 1 'Prescaler 8->16Hz or 1->128Hz On Ovf2 _isr_t2ovf Nosave Enable Ovf2 '...but if you love 32hz debounce use CTC (max. Verzögerung 4/32=0,125s) ' Tccr2 = &B0000_1010 'CTC und Prescale=8 @32768Hz ' Assr = &B0000_1000 'clocked from crystal Oscillator ' Ocr2 = 127 '32768/(8*(127+1))=32Hz ' On Oc2 _isr_t2ovf Nosave 'Interrupt NOSAVE_ISR! ' Enable Oc2
in der ISR ist dann die folgende Zeile der Frequenz entsprechend anzupassen:
andi r16,128-1 'if _tick128 is not an 128 multiple
Warum eine ISR in Assembler programmieren?
Ein gutes AVR-Programm hängt nicht irgendwo im Code fest. Insbesonders sollten ISR sehr schnell durchlaufen werden, um andere Prozesse nicht zum Stocken zu bringen. (Infrarotsensor auslesen etc).
Stehen nach einem Interrupt größere Sachen an (Print, LCD etc), dann setzt man in der ISR nur ein Flag und bearbeitet das Problem in der Main. Im Beispiel 2 wurde das Flag flagRTC_Changed gesetzt, um jede Sekunde das LCD zu aktualisieren. In der Variablen Key_press wird nach jedem Interrupt-Ereignis (Aufwachen aus Powersave) nachgesehen, ob eine neue Taste erkannt wurde.
Wird in Bascom eine ISR ohne dem Zusatz NOSAVE programmiert, sichert Bascom fast alle Register auf den Stack. Das kostet Zeit und führt zu Stacküberläufen, wenn man den Stackbereich nicht deutlich anhebt. Hier ein Bascom-Code Schnipsel einer solchen ISR.
PUSH R0 Push register on stack PUSH R1 PUSH R2 PUSH R3 PUSH R4 PUSH R5 PUSH R7 PUSH R10 PUSH R11 PUSH R16 PUSH R17 PUSH R18 PUSH R19 PUSH R20 PUSH R21 PUSH R22 PUSH R23 PUSH R24 PUSH R25 PUSH R26 PUSH R27 PUSH R28 PUSH R29 PUSH R30 PUSH R31 IN R24,SREG In from I/O location PUSH R24 Push register on stack 'eigener Code in der ISR POP R24 Pop register from stack OUT SREG,R24 Out to I/O location POP R31 Pop register from stack POP R30 POP R29 POP R28 POP R27 POP R26 POP R25 POP R24 POP R23 POP R22 POP R21 POP R20 POP R19 POP R18 POP R17 POP R16 POP R11 POP R10 POP R7 POP R5 POP R4 POP R3 POP R2 POP R1 POP R0 RETI
Das sind 26 zusätzliche Byte auf dem Stack. Unser Beispiel 2 benötigt nur 4 Register und SREG, d.h. 5 Byte auf dem Stack.
Siehe auch
Weblinks
* [1] - AVR-Tutorial: Tasten * [2] - Entprellung * [3] - Tasten entprellen - Bulletproof