Shop OBEX P1 Docs P2 Docs Learn Events
there has got to be a better way... — Parallax Forums

there has got to be a better way...

skynuggetskynugget Posts: 172
edited 2009-10-23 07:37 in General Discussion
hey all, i have some code here that polls six buttons, then spits out a midi code that corresponds with the button pressed. After the serial string is sent, it lights up one of 6 led's also corresponding to the last button pressed.

the code works great, but it's rather unevolved and longwinded. is there a better way to do this perhaps with a for next loop?

thanks in advance!
Btn1            PIN    RB.0    input PULLUP SCHMITT
Btn2            PIN RB.1    input PULLUP SCHMITT
Btn3            PIN RB.2    input PULLUP SCHMITT
Btn4            PIN RB.3    input PULLUP SCHMITT
Btn5            PIN RB.4    input PULLUP SCHMITT
Btn6            PIN RB.5    input PULLUP SCHMITT

SW1                PIN RB.6    input PULLUP SCHMITT

LEDS            PIN RC
LED1            PIN    RC.0    
LED2            PIN RC.1    
LED3            PIN RC.2    
LED4            PIN RC.3    
LED5            PIN RC.4    
LED6            PIN RC.5

' -------------------------------------------------------------------------
' Constants
' -------------------------------------------------------------------------

open                         con 1
closed                     con 0

Baud        CON    "OT31250"        ' for MIDI

Channel     CON     0
CtrChange CON        $C0    | Channel

' -------------------------------------------------------------------------
' Variables
' -------------------------------------------------------------------------
idx var byte

tmpB1        VAR    Byte            ' for subs/funcs
tmpB2        VAR    Byte
tmpW1        VAR    Word


' =========================================================================
  PROGRAM Start
' =========================================================================


' -------------------------------------------------------------------------
' Subroutine / Function Declarations
' -------------------------------------------------------------------------

WAIT_MS    SUB    1, 2            ' replaces PAUSE

TX_BYTE        SUB    1            ' transmit a byte
TX_STR        SUB    2            ' transmit a string

CNG_PGM        SUB    1            ' Change MIDI program



' -------------------------------------------------------------------------
' Program Code
' -------------------------------------------------------------------------

Start:


Main:
    IF btn1 = closed THEN                                         
        WAIT_MS 20                                    'debounce
        IF btn1 = closed THEN
            LOW LEDS
            CNG_PGM 0
            WAIT_MS 1000                            'allow for program load time
            HIGH LED1
            DO WHILE btn1 = closed        'wait for button release
                WAIT_MS 20
            LOOP 
        ENDIF
    ENDIF
  
    IF btn2 = closed THEN                                         
        WAIT_MS 20                                    'debounce
        IF btn2 = closed THEN
            LOW LEDS
            CNG_PGM 1
            WAIT_MS 1000                            'allow for program load time
            HIGH LED2
            DO WHILE btn2 = closed        'wait for button release
                WAIT_MS 20
            LOOP 
        ENDIF
    ENDIF
  
    IF btn3 = closed THEN                                         
        WAIT_MS 20                                    'debounce
        IF btn3 = closed THEN
            LOW LEDS
            CNG_PGM 2
            WAIT_MS 1000                            'allow for program load time
            HIGH LED3
            DO WHILE btn3 = closed        'wait for button release
                WAIT_MS 20
            LOOP 
        ENDIF
    ENDIF

    IF btn4 = closed THEN                                         
        WAIT_MS 20                                    'debounce
        IF btn4 = closed THEN
            LOW LEDS
            CNG_PGM 3
            WAIT_MS 1000                            'allow for program load time
            HIGH LED4
            DO WHILE btn4 = closed        'wait for button release
                WAIT_MS 20
            LOOP 
        ENDIF
    ENDIF

    IF btn5 = closed THEN                                         
        WAIT_MS 20                                    'debounce
        IF btn5 = closed THEN
            LOW LEDS
            CNG_PGM 4
            WAIT_MS 1000                            'allow for program load time
            HIGH LED5
            DO WHILE btn5 = closed        'wait for button release
                WAIT_MS 20
            LOOP 
        ENDIF
    ENDIF
    
IF btn6 = closed THEN                                         
        WAIT_MS 20                                    'debounce
        IF btn6 = closed THEN
            LOW LEDS
            CNG_PGM 5
            WAIT_MS 1000                            'allow for program load time
            HIGH LED6
            DO WHILE btn6 = closed        'wait for button release
                WAIT_MS 20
            LOOP 
        ENDIF
    ENDIF

GOTO Main ' loop


Post Edited (skynugget) : 10/20/2009 12:26:23 PM GMT

Comments

  • BeanBean Posts: 8,129
    edited 2009-10-20 11:16
    I don't think your code is that bad. In fact it's quite logical and easy to read.

    You COULD do some fancy stuff with bit masks and such, but it would make it alot less readable.

    Bean.

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Does that byte of memory hold "A", 65, $41 or %01000001 ?
    Yes it does...


    ·
  • skynuggetskynugget Posts: 172
    edited 2009-10-20 12:21
    well i stand corrected. thanks bean, it means alot to get your stamp of approval [noparse]:)[/noparse]
  • JonnyMacJonnyMac Posts: 9,212
    edited 2009-10-21 16:31
    As Been points out there is nothing at all wrong with you code. If, however, you find yourself pressed for space at some point you can reduce the code used using "fancy stuff with bit masks." It is not as easy to read as your verbose code, but it does use a lot less space.

    Here's the Main section:

    Main:
      tmpBtns = GET_BTNS
      IF tmpBtns = %00000000 THEN Main              ' wait for button press
    
      FOR idx = 0 TO 5                              ' loop through six buttons
        mask = 1 << idx                             ' create mask for input
        check = tmpBtns & mask                      ' test the button
        IF check > 0 THEN                           ' was button pressed?
          Leds = mask                               ' yes, light its LED
          CNG_PGM idx                               ' change selected program
          DO
            tmpBtns = GET_BTNS
          LOOP UNTIL tmpBtns = %00000000            ' force release of all buttons
          GOTO Main
        ENDIF
      NEXT
    
      GOTO Main
    


    As you can see, this calls a function called GET_BTNS which handles the input debouncing:

    GET_BTNS        FUNC    1, 0, 0
    



    FUNC GET_BTNS
      gbIdx         VAR     tmpB1
      gbResult      VAR     tmpB2
    
      gbReseult = %00111111                         ' assume pressed
      FOR gbIdx = 1 TO 25                           ' 25ms debounce
        DELAY_MS 1
        gbResult = gbResult &~ RB                   ' scan inputs
      NEXT
    
      RETURN gbResult
      ENDFUNC
    


    Just a note here: You're using active-low inputs. The GET_BTNS routine deals with that and then returns a value with "1" indicating a button press -- it's a little easier to work with in the main code. What I'm saying is that you don't have to change your hardware; this routine will work fine. Just remember that if button 1 is pressed the fuctnion will return %00000001.

    Once you get your project working save a new version and try this to see how you like it. Even if you don't use it here, masking tricks are handy to have up one's sleeve.

    Post Edited (JonnyMac) : 10/21/2009 7:48:07 PM GMT
  • skynuggetskynugget Posts: 172
    edited 2009-10-21 21:12
    JohnnyMac you rock! thanks!

    I have always had problems with bit masks till now. I really wasn't concerned about space for this stage of the project, i just need to get it built and working. As I have time I would like to incorporate the ISR code from the Mo MIDI project to get status from the device, so it puts me a leg up. (If i can ever get the manual)

    Here's the updated code that does the exact same thing above. Hope someone learns something about those fancy bitmasks!

    DEVICE          SX28, OSCXT2, TURBO, STACKX, OPTIONX, BOR42
    FREQ            20_000_000
    ID              "BTN2MIDI"
    
    
    ' -------------------------------------------------------------------------
    ' I/O Pins
    ' -------------------------------------------------------------------------
    
    TX                PIN    RA.1     INPUT
    
    Btns            PIN RB    input PULLUP SCHMITT
    
    LEDS            PIN RC output
    
    
    ' -------------------------------------------------------------------------
    ' Constants
    ' -------------------------------------------------------------------------
    Baud            CON        "OT31250"        ' for MIDI
    
    Channel     CON     0
    CtrChange CON        $C0    | Channel
    
    ' -------------------------------------------------------------------------
    ' Variables
    ' -------------------------------------------------------------------------
    
    tmpB1        VAR    Byte            ' for subs/funcs
    tmpB2        VAR    Byte
    tmpW1        VAR    Word
    
    tmpBtns           VAR Byte
    idx            VAR    Byte
    mask        VAR Byte
    check        VAR Byte
    
    ' =========================================================================
      PROGRAM Start
    ' =========================================================================
    
    ' -------------------------------------------------------------------------
    ' Subroutine / Function Declarations
    ' -------------------------------------------------------------------------
    
    WAIT_MS        SUB        1, 2        ' replaces PAUSE
    
    TX_BYTE        SUB        1                ' transmit a byte
    TX_STR        SUB        2                ' transmit a string
    
    CNG_PGM        SUB        1                ' Change MIDI program
    
    GET_BTNS  FUNC    1, 0, 0 ' Poll for button press
    
    ' -------------------------------------------------------------------------
    ' Program Code
    ' -------------------------------------------------------------------------
    
    Start:
    
    Main:
        
        tmpBtns = GET_BTNS
      IF tmpBtns = %00000000 THEN Main              ' wait for button press
        
      FOR idx = 0 TO 5                              ' loop through six buttons
        mask = 1 << idx                             ' create mask for input
        check = tmpBtns & mask                      ' test the button
        IF check > 0 THEN                           ' was button pressed?
          CNG_PGM idx                               ' yes, change selected program
          wait_ms 1000                ' pause for load time on device
        leds = mask                                  ' light its LED
        DO
            tmpBtns = GET_BTNS
            LOOP UNTIL tmpBtns = %00000000            ' force release of all buttons
          GOTO Main
        ENDIF
      NEXT
     
    
    GOTO Main ' loop
    
    
    ' -------------------------------------------------------------------------
    ' Subroutine / Function Code
    ' -------------------------------------------------------------------------
    
    ' Use: WAIT_MS duration
    
    SUB WAIT_MS 
      IF __PARAMCNT = 1 THEN
        tmpW1 = __PARAM1
      ELSE
        tmpW1 = __WPARAM12
      ENDIF
      PAUSE tmpW1
      ENDSUB
    
    ' -------------------------------------------------------------------------
    
    ' Use: TX_BYTE aByte
    
    SUB TX_BYTE
      SEROUT TX, Baud, __PARAM1
      ENDSUB
    
    ' -------------------------------------------------------------------------
    
    ' Use: TX_STR [noparse][[/noparse]String | Label]
    ' -- moves z-String to tx buffer
    ' -- "String" is an embedded string
    ' -- "Label" is a DATA label with z-String
    
    SUB TX_STR
      tmpW1 = __WPARAM12        ' get address of start
      DO
        READINC tmpW1, tmpB1    ' read a character
        IF tmpB1 = 0 THEN EXIT        ' done?
        TX_BYTE tmpB1            ' no, transmit the char
      LOOP
      ENDSUB
      
    ' -------------------------------------------------------------------------
    
    ' Use: CNG_PGM program#
    
    SUB CNG_PGM
      tmpB1 = __PARAM1
        
      TX_BYTE CtrChange            ' Control Change
      TX_BYTE tmpB1            ' Program Number
      
      ENDSUB
      
    
    ' -------------------------------------------------------------------------
    
    ' Use: var = GET_BTNS
    
    FUNC GET_BTNS
      gbIdx         VAR     tmpB1
      gbResult      VAR     tmpB2
    
      gbResult = %11000000                  ' bit 6,7 not used, assume pressed
      
        FOR gbIdx = 1 TO 25               ' 25ms debounce
          WAIT_MS 1
          gbResult = gbResult | RB          ' scan inputs
      NEXT
    
        GbResult = gbResult ^ $FF        ' invert (1 = pressed)
      
        RETURN gbResult
      
        ENDFUNC
    
    ' -------------------------------------------------------------------------
    
    

    Post Edited (skynugget) : 10/21/2009 9:19:58 PM GMT
  • JonnyMacJonnyMac Posts: 9,212
    edited 2009-10-22 18:41
    Since you're open to learning and I use SX/B 2.0 every day (just delivered an 88-channel, networked animation control system last week that uses SX28s on an RS-485 network) I used your program as a brain warm-up before I start my own coding today. What I've done is bring your program up to the latest 2.0 standards. Seeing it may help you make

    Hint: If one SUB/FUNC A is called by SUB/FUNC B then A should appear further down the listing -- this lets you use conditional compilation of routines to preserve space as your projects get bigger. Note, too, how with SX/B 2.0 the WAIT_MS subroutine is simplified to one [noparse][[/noparse]working] line. You'll also note that I replaced the FOR-NEXT loop with manual code; this is a personal style thing as I think one should limit the amount of code enclosed in any loop structure.

    DEVICE          SX28, OSCXT2, BOR42
    FREQ            20_000_000
    
    ID              "BTN2MIDI"
    
    
    ' -------------------------------------------------------------------------
    ' I/O Pins
    ' -------------------------------------------------------------------------
    
    TX              PIN     RA.1
    
    Btns            PIN     RB INPUT  PULLUP SCHMITT
    LEDS            PIN     RC OUTPUT
    
    
    ' -------------------------------------------------------------------------
    ' Constants
    ' -------------------------------------------------------------------------
    
    Baud            CON     "OT31250"               ' for MIDI
    
    Channel         CON     0
    CtrChange       CON     $C0 | Channel
    
    
    ' -------------------------------------------------------------------------
    ' Variables
    ' -------------------------------------------------------------------------
    
    tmpBtns         VAR     Byte
    chan            VAR     Byte
    mask            VAR     Byte
    check           VAR     Byte
    
    tmpB1           VAR     Byte                    ' for subs/funcs
    tmpB2           VAR     Byte
    tmpW1           VAR     Word
    
    
    ' -------------------------------------------------------------------------
    ' Subroutine / Function Declarations
    ' -------------------------------------------------------------------------
    
    GET_BTNS        FUNC    1, 0, 0, Byte           ' Poll for button press
    
    CNG_PGM         SUB     1, 1,    Byte           ' Change MIDI program
    
    TX_STR          SUB     2, 2,    Word           ' transmit a string
    TX_BYTE         SUB     1, 1,    Byte           ' transmit a byte
    
    WAIT_MS         SUB     2, 2,    Word           ' replaces PAUSE
    
    
    ' =========================================================================
      PROGRAM Main
    ' =========================================================================
    
    Main:
      tmpBtns = GET_BTNS
      IF tmpBtns = %00000000 THEN Main              ' wait for button press
    
      chan = 0                                      ' start with Button 1
    
    Check_ChBtn:
      mask = 1 << chan                              ' create mask for input
      check = tmpBtns & mask                        ' test the button
      IF check > 0 THEN                             ' was button pressed?
        CNG_PGM chan                                ' yes, change selected program
        WAIT_MS 1000                                ' pause for load time on device
        Leds = mask                                 ' light its LED
        DO
          tmpBtns = GET_BTNS
        LOOP UNTIL tmpBtns = %00000000              ' force release of all buttons
        GOTO Main
      ENDIF
      INC chan                                      ' next channel
      IF chan < 6 THEN Check_ChBtn                  ' more?
        GOTO Main
    
    
    ' -------------------------------------------------------------------------
    ' Subroutine / Function Code
    ' -------------------------------------------------------------------------
    
    ' Use: var = GET_BTNS
    
    FUNC GET_BTNS
      '{$IFUSED GET_BTNS}
      gbIdx         VAR     tmpB1
      gbResult      VAR     tmpB2
    
      gbResult = %00111111                          ' bit 6,7 not used, assume pressed
    
      FOR gbIdx = 1 TO 25                           ' 25ms debounce
        WAIT_MS 1
        gbResult = gbResult &~ RB                   ' scan inputs
      NEXT
    
      RETURN gbResult
      '{$ENDIF}
      ENDFUNC
    
    ' -------------------------------------------------------------------------
    
    ' Use: CNG_PGM program#
    
    SUB CNG_PGM
      '{$IFUSED CNG_PGM}
      newPgm        VAR     tmpB1
    
      newPgm = __PARAM1
    
      TX_BYTE CtrChange                             ' Control Change
      TX_BYTE newPgm                                ' Program Number
      '{$ENDIF}
      ENDSUB
    
    ' -------------------------------------------------------------------------
    
    ' Use: TX_STR [noparse][[/noparse]String | Label]
    ' -- "String" is an embedded string
    ' -- "Label" is a DATA label with z-String
    
    SUB TX_STR
      '{$IFUSED TX_STR}
      tsAddr        VAR     tmpW1
      tsChar        VAR     __PARAM1
    
      tsAddr = __WPARAM12                           ' get string address
    
      DO
        READINC tsAddr, tsChar                      ' get a character
        IF tsChar = 0 THEN EXIT                     ' abort on 0
        TX_BYTE tsChar
      LOOP
      '{$ENDIF}
      ENDSUB
    
    ' -------------------------------------------------------------------------
    
    ' Use: TX_BYTE aByte
    ' -- shell for SEROUT
    
    SUB TX_BYTE
      '{$IFUSED TX_BYTE}
      SEROUT TX, Baud, __PARAM1
      '{$ENDIF}
      ENDSUB
    
    ' -------------------------------------------------------------------------
    
    ' Use: WAIT_MS milliseconds
    ' -- replaces PAUSE
    
    SUB WAIT_MS
      '{$IFUSED WAIT_MS}
      PAUSE __WPARAM12
      '{$ENDIF}
      ENDSUB
    
    ' -------------------------------------------------------------------------
    

    Post Edited (JonnyMac) : 10/23/2009 7:41:52 AM GMT
  • skynuggetskynugget Posts: 172
    edited 2009-10-23 00:58
    way cool.. *salute
    i don't quite understand how the compile time directive saves space? the way i understand it $ifused will not compile the code enclosed if its not called? which has seemed a little odd to me. why write code you're not going to use? got time to set me straight?
  • JonnyMacJonnyMac Posts: 9,212
    edited 2009-10-23 07:37
    For example, you have TX_STR in the program but, for the moment, anyway, it's not used. If you look at the compiled code (Ctrl + L) you'll see that TX_STR compiles to an empy routine -- saves space. By using {$IFUSED name}...{$ENDIF} you can create libraries that have routines that may or may not be used.
Sign In or Register to comment.