Shop OBEX P1 Docs P2 Docs Learn Events
Polling for button inputs while delaying other actions? — Parallax Forums

Polling for button inputs while delaying other actions?

Donnie BarnesDonnie Barnes Posts: 6
edited 2006-12-15 17:14 in BASIC Stamp
I'm going to do some more complex stuff, but for the sake of argument, let's say I have a serial LCD and a button connected to the BS2p. The serial LCD has a backlight on/off command you can send it. I also have an AD592 temp probe connected.

Now, I can talk to the LCD fine. I can get temp info from the AD592 fine. I can sample the AD592 every five seconds and display the results on the LCD fine. What I can't quite figure out is how to poll the button state and use that info to set the backlight state very well. I got something that sort of works, but I think I need to see some fairly similar sample code that *really* works to better know what I'm doing. This lack of interrupt thing is what's getting me. Any pointers to sample code? Or general instruction?

An ugly code snippet of what I have:

Main:

  LOW 5 ' Discharge the capacitor.
  RCTIME 5, 0, rct ' Time for the volts to rise to 1.3 V.
  TK = Kal/rct*10 + (Kal//rct*10/rct) ' Calculate Kelvin
  TC = TK - 273 ' and Celsius.
  TF = ((TC*18) + 320) / 10 ' and Fahrenheit
  
  SEROUT TX, LcdBaud, [noparse][[/noparse]LcdLine1, DEC TC, " degrees C", LcdLine2, DEC TF, " degrees F"]

  FOR COUNTER = 1 TO 200 :
    IF IN7 = 0 THEN
      IF BLSTATE = 1 THEN
        SEROUT TX, LcdBaud, [noparse][[/noparse]LcdBLOn]
        BLSTATE = 0
        PAUSE 1000
      ELSE
        SEROUT TX, LcdBaud, [noparse][[/noparse]LcdBLoff]
        BLSTATE = 1
        PAUSE 1000
      ENDIF
    ELSE
      PAUSE 25
    ENDIF
  NEXT
  GOTO Main
  END





That sort of works, but you can still miss the button press easily and you can't press the button more than once per second. There's got to be a better way. Basically, I only want to update the temp every five seconds, but I want to be able to turn the backlight on and off any time (and this will lead into more complex stuff, but I have to get this issue "better" first).


--Donnie

Comments

  • ZootZoot Posts: 2,227
    edited 2006-12-12 20:53
    Why not set up the code for the back light in a separate slot, then use POLLRUN to check the button input and run that slot (which would turn the light on/off). At the end of your POLLRUN slot, put a RUN 0 to get you back to your main program.

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    When the going gets weird, the weird turn pro. -- HST
  • Donnie BarnesDonnie Barnes Posts: 6
    edited 2006-12-12 21:02
    Holy smokes, I knew it would be something that "simple." I didn't notice the POLL* commands, but now that you pointed me to it, I read them in the manual and that appears to be exactly what I was after. Thanks!


    --Donnie
  • Mike GreenMike Green Posts: 23,101
    edited 2006-12-12 21:07
    The trick is to get rid of the 1 second pauses or, for that matter, all the pauses. What you really want is to debounce the button, only update the temp every few seconds, etc. If you don't need a precise period for the temperature updates and button debouncing, you can use the basic average RCTIME timing for your loop time "tick" and keep a count of the number of times through the loop. Use a word variable and just increment it every time through the loop. On each loop, check the button state. If it's up (not pressed), save the current loop count and clear a bit used to indicate you've responded to the button. If it's down (pressed), look at the elapsed time (current loop count - saved count). If the time is greater than something that translates into 50-100ms and your bit is still cleared, then you have a valid press, so set the bit mentioned to indicate that you've already seen the button press and toggle the backlight state (BLSTATE and sending the state to the LCD).

    For the temp update, keep a variable with the loop count at the last temp update. Each time through the loop, look at the elapsed time. If it's greated than some value, update the LCD and reset the saved loop count to the current count.

    You can add other "timed" actions in the same or similar ways. You can add other buttons as well. As the loop gets busier, you may need to either go to a faster processor or go to a tiered setup where you check some things every time through the loop, but check some things maybe every 4 times through the loop or every 16 times. That way you might check AA on one time through the loop, BB on the second time, CC on the third time, and DD on the fourth time. ZZ you might check every time through the loop, etc.
  • Chris SavageChris Savage Parallax Engineering Posts: 14,406
    edited 2006-12-13 15:30
    Mike,
    ·
    ·· You beat me to the punch on mentioning removing the PAUSE commands.· =)·
    ·
    Donnie,
    ·
    ·· What I would do is implement a passive counter variable that is reset whenever the button is pushed.· That same event also cause the backlight to come on…Once the timer reaches a certain value you turn the backlight off.· You can use the MAX operator to limit the count and keep it from rolling over.· This same counter can also implement your debounce that Mike mentioned.
    ·
    ·· So think of it like this.· You have your loop running with no delays in it.· Read the sensor, update the display, check for button presses and increment the counter.· While incrementing the counter you limit it to some value less than its maximum value.·· You will have a conditional check for two things…the first is if the button is pressed.· When it is you can reset the counter, wait for so many ‘ticks’ to go by (this is your debounce) then turn on the backlight and continue the loop.· Your other conditional is simply waiting for the counter to reach its upper limit (which will be less than the max value you set).· When that has been reached the backlight turns off.· Ticks refers to passes through the loop, in this case, and the timing is somewhat indeterminate without experimenting.· I hope this offers some useful information.· Take care.

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    Chris Savage
    Parallax Tech Support
  • Donnie BarnesDonnie Barnes Posts: 6
    edited 2006-12-14 02:11
    Okay, I think I understand, but here's the code I've got:

    COUNTER = 1500
    DEBOUNCE = 0
    BLSTATE = 0
    
    SEROUT TX, LcdBaud, [noparse][[/noparse]LcdCls, LcdOn1]
    PAUSE 5                                 ' recommended pause after a Cls
    
    Main:
      LOW 5                                 ' Discharge the capacitor.
      RCTIME 5, 0, rct                      ' Time for the volts to rise to 1.3 V.
      TK = Kal/rct*10 + (Kal//rct*10/rct)   ' Calculate Kelvin
      TC = TK - 273                         ' and Celsius.
      TF = ((TC*18) + 320) / 10             ' and Fahrenheit
    
      IF COUNTER = 1500 THEN
        SEROUT TX, LcdBaud, [noparse][[/noparse]LcdLine1, DEC TC, " degrees C", LcdLine2, DEC TF, " degrees F"]
        COUNTER = 0
      ENDIF
    
      IF IN7 = 0 THEN                      ' button pressed
        IF DEBOUNCE = 60 THEN
          IF BLSTATE = 1 THEN
            SEROUT TX, LcdBaud, [noparse][[/noparse]LcdBLOn]
            BLSTATE = 0
          ELSE
            SEROUT TX, LcdBaud, [noparse][[/noparse]LcdBLoff]
            BLSTATE = 1
          ENDIF
          DEBOUNCE = 0
        ENDIF
      ENDIF
    
      COUNTER = COUNTER + 1
      DEBOUNCE = DEBOUNCE + 1
      GOTO Main
      END
    
    



    Problem is that I think it executes too fast on the iterations where it doesn't update the screen and such. I now get a semi-accurate first reading, and then garbage for every reading after that. If I put a DEBUG in there to spit out a line of info on every iteration, the data stays fairly good (I presume because the overhead to spit that entire line of text out to the serial port is fairly high compared to a few compare instructions and then a loop). The backlight debouncing and operation seems okay, though perhaps needs a bit more tweaking on the timing part.

    Oddly enough, if I comment out the loop around the SEROUT, then the data doesn't get munged at all. But the backlight timing is now way off (and doesn't seem repairable with the current setup) and won't work. Of course, the screen updates a lot more than I'd really like, too.

    Thoughts? I'm sure my code could be more elegant, but I'm also guessing I'm still missing something. Or maybe my capacitor type was a bad choice...


    --Donnie
  • Vern GranerVern Graner Posts: 337
    edited 2006-12-14 03:19
    Donnie Barnes said...
    I'm going to do some more complex stuff, but for the sake of argument, let's say I have a serial LCD and a button connected to the BS2p.
    Question: Do you want the button to control the state of the back light (i.e. button down= light on, button up = light off) or do you want the button to cause the light to toggle state (i.e. button down then up = light on, button down then up = light off) or to casue the backlight to come on for a short period of time, and then go out (i.e. button down, button up = backlight on for 5 seconds)? The answer to this question will control what you do in the button detect loop...

    Any way you choose above, I think I would approach this by making the loop about the button itself. I would have a counter set up to count "iterations" of the loop and then through trial/error discover how many iterations (ticks) of the loop=1 second, and make that a "trigger" value.

    Then setup a "seconds" counter would be iterated whenever the "ticks" is equal to the "trigger" value. When the Seconds counter reaches "5" (or whatever # of seconds you prefer as the delay between temperature updates) then run a suibroutine that reads the temp and updates the LCD.

    Inside the loop itself, have a flag for Back Light status (a bit sized variable will do). If the button is pressed, change the state of the backlight and toggle the Back Light Status bit. The Back Light Status bit is used to make sure the command for the LCD (turn backlight on/off) is only sent ONE TIME for each change of button state. smile.gif I have banged together some code that shjould illustrate the point (and might work!). Note: This is untested! smile.gif

      ' Variables
        BackLightFlag    VAR   Bit  ' 1 bit to hold the backlight status
        TICKS            VAR   Word ' big variable to count loop iterations
        SECONDS          VAR   Nib  ' variable up to 16 seconds (make it a byte if you need more)
    
      'Constants
        Trigger          CON   1780 ' this value will need to be determined !
        TRUE             CON   1    ' for comparisons
        FALSE            CON   0    ' IBID :)
    
    MAIN:
    IF PushButton = TRUE THEN
      IF BacklightFlag = FALSE THEN
        SEROUT LCD, LCDbaud, [noparse][[/noparse]LcdBLon]
        BacklightFlag = TRUE
      ENDIF
    ELSE
      IF BacklightFlag = TRUE THEN
        SEROUT LCD, LCDbaud, [noparse][[/noparse]LcdBLoff]
        BacklightFlag = FALSE
      ENDIF
    ENDIF
    
    TICKS=TICKS+1          ' add one for every time through the loop
    IF TICKS=TRIGGER THEN  ' if we reach the # of ticks that are equal
      SECONDS=SECONDS+1    '  to 1 second, iterate second counter
      IF SECONDS = 5 THEN  ' If we have reach 5 seconds then
       GOSUB TimeCheck     ' time to check temp & update the LCD
       SECONDS=0           ' Reset the seconds couter
       TICKS=0             'reset the TICKS counter
      ENDIF
    ENDIF
    
    GOTO MAIN
    
    'Subroutines
    
    TimeCheck:
      LOW 5                                 ' Discharge the capacitor.
      RCTIME 5, 0, rct                      ' Time for the volts to rise to 1.3 V.
      TK = Kal/rct*10 + (Kal//rct*10/rct)   ' Calculate Kelvin
      TC = TK - 273                         ' and Celsius.
      TF = ((TC*18) + 320) / 10             ' and Fahrenheit
      SEROUT TX, LcdBaud, [noparse][[/noparse]LcdLine1, DEC TC, " degrees C", LcdLine2, DEC TF, " degrees F"]
      RETURN
    
    



    Hope this helps! smile.gif

    Vern

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    Vern Graner CNE/CNA/SSE    | "If the network is down, then you're
    Senior Systems Engineer    | obviously incompetent so why are we
    Texas Information Services | paying you? Of course,if the network
    http://www.txis.com        | is up, then we obviously don't need
    Austin Office 512 328-8947 | you, so why are we paying you?" ©VLG
    
    

    Post Edited (Vern) : 12/14/2006 3:28:45 AM GMT
  • Tracy AllenTracy Allen Posts: 6,662
    edited 2006-12-14 08:04
    POLLing commands can latch the button event, in the sense that the stamp remembers that the button was pressed, even after it is released. Bit 7 of location 128 in the scratchpad RAM is read only and stores the event-- change of state on pin p7. Here is a snippet that uses POLLIN and POLLMODE 10 to detect the button. The backlight toggles only when the button goes from 0 to 1,

    state VAR byte
    idx   VAR byte
    x     VAR bit
    
    top:
     x=~in7           ' inverse of current in7 state
      POLLIN 7,x       ' poll for change of state
      POLLMODE 10     ' polling is latched, no output effect, but affects bit 7 of SPram location 128
    DO
      GOSUB AD592display    ' acquire & display from AD592
       FOR idx=1 to 100   ' 100 units of 50 milliseconds
        PAUSE 49
        GET 128,state     ' from scratchpad
        IF state.bit7 THEN    ' true if button goes to target state
           IF x=1 THEN GOSUB ToggleLCD  ' true if target state was in7=1
           x=~x   ' poll for opposite state 
           POLLIN 7,x
           POLLMODE 10    ' this resets locations in SPram
        ENDIF
      NEXT
    LOOP
    

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    Tracy Allen
    www.emesystems.com

    Post Edited (Tracy Allen) : 12/14/2006 8:20:29 AM GMT
  • Chris SavageChris Savage Parallax Engineering Posts: 14,406
    edited 2006-12-14 15:50
    Donnie,
    ·
    ·· I am impressed…I usually don’t explain passive counters very well, but your display routine uses it perfectly.· It will update the LCD every 1500 iterations through the loop.· The problem I see is with the button routine.· DEBOUNCE will be at an indeterminate value when you press the button and the check for a value is inside of a conditional, which means it may not activate most times.·
    ·
    ·· Using DEBUG can you tell if the data is getting garbled outside the LCD Print routine?· Or does adding the DEBUG line fix what you see on the LCD as well?· Can you post a fully working version of this code?· As a side-note are you sure about your value of 5 in your RCTIME command?

    EDIT: Just noticied Tracy's post...If you have a BS2p that might be something to look into as well.

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    Chris Savage
    Parallax Tech Support
  • Donnie BarnesDonnie Barnes Posts: 6
    edited 2006-12-14 19:32
    Haven't tried anything new yet (and may not for a couple days), but...

    I originally thought the POLL thing was cool, then upon reading more (and getting further responses) liked the idea of not using separate programs in different slots. That seems a bit hacky to me. But I didn't read further at the time on POLL to know I didn't *need* to use separate program slots. I do have a bs2p, so Tracy's solution does look pretty good. I'll try that.

    As for the 5 in my RCTIME, that came straight from the sample code for the AD592 on the Parallax web site. The constant from the sample code seems to need to vary fairly wildly depending on how tight these loops are, though, which makes me wonder about my electronics side a little. Could be that my capacitor isn't what I think it is, and I could also have a lot of noise affecting things on the professional developer board (I didn't clip legs on the resistor or capacitor, etc). I haven't tried calibrating it with the cold bath yet, but I'm just going by getting reasonably close to what I think the room temp is.

    Oh, I'm also not sure why in my code DEBOUNCE would ever be indeterminate. I set it to zero to start with, I increment it every tick (or trip through the main loop), and I reset it to zero if it's held long enough. What am I missing there?


    --Donnie
  • Chris SavageChris Savage Parallax Engineering Posts: 14,406
    edited 2006-12-14 20:26
    Donnie,
    ·
    ·· What appears to be wrong with the debounce section is that yes, you are incrementing the counter and yes, you do reset it, but only if it was at 60 when it entered the loop.· Say during the loop the DECOUNCE counter is at 61 when you press the button…now your code will have to loop all the way through 65476 more iterations to get to the inner loop that resets.· Then, depending on how long you hold the button down it may actually run that loop a few times while the button is down.· You don’t actually want to increment your DEBOUNCE counter until the button is pressed.· It should be reset when the button is first pressed, or optionally, when released.
    ·
    ·· I didn’t catch the reference to the AD592, so maybe there is a small problem with your circuit or it could be the tolerance of the capacitor.· I hope this helps.· Take care.

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    Chris Savage
    Parallax Tech Support
  • Donnie BarnesDonnie Barnes Posts: 6
    edited 2006-12-15 02:38
    So how about a change to this:

      IF IN7 = 0 THEN                      ' button pressed
        IF DEBOUNCE = 85 THEN
          IF BLSTATE = 1 THEN
            SEROUT TX, LcdBaud, [noparse][[/noparse]LcdBLOn]
            BLSTATE = 0
          ELSE
            SEROUT TX, LcdBaud, [noparse][[/noparse]LcdBLoff]
            BLSTATE = 1
          ENDIF
          DEBOUNCE = 0
        ENDIF
        IF PUSHED = 1 THEN
          DEBOUNCE = DEBOUNCE + 1
        ENDIF
        PUSHED = 1
      ELSE
        PUSHED = 0
      ENDIF
    
    



    I see what you meant about how DEBOUNCE could get incremented by the "dirt" on the up/down side. With this it seems like it can only get incremented if the previous loop through was positive, which should actually do some debouncing rather than just going with 85 possibly-non-contiguous instances of pushed state. Right? (PUSHED is a Bit that was set to zero initially, too.)

    This seems to work better, but still occasionally "misses" a button press (and I've futzed with the DEBOUNCE limit anywhere from 25 to 150...25 does seem to short and flashes if you hold the button very long and 150 seems too long as you *have* to hold it a bit to change state, but no number seems "just right." Obviously I could still have a dumb error in my code. [noparse]:)[/noparse]


    --Donnie
  • KatyBriKatyBri Posts: 171
    edited 2006-12-15 03:16
    Perhaps, instead of trying to read the button when it changes,·have the button's state remain until it is pressed again, and handle the state change in your code.
    ·
    For example, add a cmos latch to your button and tie the latch's output to the STAMP pin you are reading. The latch would be configured to put +5vdc on its output when it is pressed, and then 0vdc the next time it is pressed. This simple toggling of the latch's output would repeat as the button is pressed. The important thing, though, is the state of the latch's output would remain constant until the button is pressed again.
    ·
    You could then in your program read the state of the latch - WHENEVER YOUR CODE GETS THE OPPORTUNITY- and store it. After the first time, it is read, you then read it and if it is different from the last time you read it, you know the button was pushed- you do something with your code, reset the previous saved value, and continue on (you don't worry about the actual state of the button or latch, only that it has changed from the last time you checked it). This makes the timing issue you mentioned much less of an issue.
  • Donnie BarnesDonnie Barnes Posts: 6
    edited 2006-12-15 04:10
    Well, yeah, the thought of just debouncing with a latch in hardware did occur to me. The big advantage of that would seem to be that any major changes to the code seems to require me to have to recalibrate the temperature probe because the timing is changing how the RC network works somehow. Either that or I've got a hardware gremlin, too (which seems unlikely given the simplicity of the circuit and components used, but is definitely still possible).

    I do have a degree in computer engineering (which is much like EE), but the problem is I haven't had to use much of the programming or hardware side of that in many years now. It's coming back to me, albeit more slowly than I'd like.


    --Donnie
  • Tracy AllenTracy Allen Posts: 6,662
    edited 2006-12-15 17:14
    You say the AD592 calibration is not stable and changes when you alter the rest of the program. To fix that ( I think) move the LOW 5 _after_ the RCTIME.

    LOW 5      'initialization
       ' ...
    
     RCTIME 5, 0, rct                      ' Time for the volts to rise to 1.3 V.
      LOW 5                                 ' Discharge the capacitor.
    



    The BS2p is faster than the BS2. Putting the LOW after, instead of before, stops the charging toward 5 volts and assures that the pin is at zero volts the next time around. Otherwise the capacitor continues charging to a level that depends on how much other stuff there is in the program and the time between the LOW 5:RCTIME is not enought to completely discharge that. It is also important to use a capacitor with low soakage. The capacitor should be a film type and the wiring fairly short and direct for best results.

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    Tracy Allen
    www.emesystems.com
Sign In or Register to comment.