Inhaltsverzeichnis
Bascom ISR in Assembler programmieren
Da immer wieder fragen zur Assembler-ISR-Programmierung in Bascom auftreten, will ich hier an einem 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: 3 Tasten Uhr in Bascom
Der nachfolgende Code verwendet kein Assembler. Es wurde zur Entprellung der Tasten der Bascom Befehl DEBONCE verwendet.
$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 - 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 '********* 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 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:
- Debounce muss ständig gepollt werden, d.h. der AVR ist zu 100% beschäftigt
- wird eine Taste gedrückt, friert der AVR für 30ms ein (andere Unterprogramme können inn der Zeit nicht angesprungen werden)
- Debounce besitzt kein Autorepeat, d.h. langes drücken einer Taste bewirkt keine Impulse
Beispiel 2: 3 Tasten Uhr in Bascom 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 liegt zu 99,9% der Zeit 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)
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: 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.
Siehe auch
Weblinks
* [1] - AVR-Tutorial: Tasten * [2] - Entprellung * [3] - Tasten entprellen - Bulletproof|