(→Bascom ISR in Assembler programmieren) |
|||
| (14 dazwischenliegende Versionen desselben Benutzers werden nicht angezeigt) | |||
| Zeile 1: | Zeile 1: | ||
| − | == Bascom ISR in Assembler programmieren == | + | == Die Hardware für die Code-Beispiele: Bascom ISR in Assembler programmieren == |
| − | Da immer wieder | + | |
| + | 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 Hardware''' | ||
| Zeile 14: | Zeile 15: | ||
Der u.g. Beispiel-Code lässt sich auf alle AVR-Starterplatinen mit Tasten+LCD schnell anpassen. | Der u.g. Beispiel-Code lässt sich auf alle AVR-Starterplatinen mit Tasten+LCD schnell anpassen. | ||
| − | == Beispiel 1: 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 40: | 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 118: | 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 == | ||
| − | + | 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. | 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 techn. Hintergrund wird in dem ausführlichen Artikel http://www.mikrocontroller.net/articles/AVR-Tutorial:_Tasten erläutert. | ||
| − | Der Code beseitigt | + | 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 | + | * 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. | ||
| + | Der Code ist umfangreich kommentiert. | ||
<pre> | <pre> | ||
$regfile = "m8def.dat" | $regfile = "m8def.dat" | ||
| Zeile 333: | 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 404: | Zeile 418: | ||
</pre> | </pre> | ||
Getestet mit Bascom Version BASCOM 1.11.9.1 [Codelänge 1482 Byte] | 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. | 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. | ||
| + | |||
| + | <pre> | ||
| + | '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 | ||
| + | </pre> | ||
| + | in der ISR ist dann die folgende Zeile der Frequenz entsprechend anzupassen: | ||
| + | <pre> | ||
| + | andi r16,128-1 'if _tick128 is not an 128 multiple | ||
| + | </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== | ||
| + | |||
| + | * [[Assembler_Einf%C3%BChrung_f%C3%BCr_Bascom-User]] | ||
| + | * [[LCD-Modul_am_AVR]] | ||
| + | |||
| + | ==Weblinks== | ||
| + | |||
| + | * [http://www.mikrocontroller.net/articles/AVR-Tutorial:_Tasten] - AVR-Tutorial: Tasten | ||
| + | * [http://www.mikrocontroller.net/articles/Entprellung] - Entprellung | ||
| + | * [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

