Aus RN-Wissen.de
Version vom 3. September 2008, 22:52 Uhr von -tomas- (Diskussion | Beiträge) (Bascom ISR in Assembler programmieren)

Wechseln zu: Navigation, Suche
Rasenmaehroboter Test

Bascom ISR in Assembler programmieren

Da immer wieder fragen zur Assembler Programmierung in Bascom auftreten, will ich hier an einem Beispiel einer 3 Tasten Uhr mit LCD Display den Weg aufzeigen.

Die Hardware

Clock Debounce.jpg

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:

Clock Debounce Board.png

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. Dazu wird der Bascom Befehl Debounce in Assembler nachgebaut. Um den AVR zu entlasten wird die Debounce Routine in die 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 obige Schwächen:

  • 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 Impulsefolge von 8Hz (beliebig einstellbar)

Die Entprellzeit beträgt übrigens im Beispiel wie bei Bascom ca. 30ms.

$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.


LiFePO4 Speicher Test