Pushbutton ON/OFF

edmunds

Senior Member
Dear all,

How would you go about implementing a single pushbutton ON/OFF control for a device?

The program flow could be something like this:

1) Battery connected;
2) Sleep;
3) If button pressed, run init: procedure and do main: continuously;
4) If button pressed for like three seconds, run shutdown: procedure and go to Sleep:;
5) If button pressed, run init: procedure and do main: continuously;
6) If button pressed for like three seconds, run shutdown: procedure and go to Sleep:;
7) etc. until battery re-connected in which case start from 1) again.

I have the button connected to B.2 on 40x2, so I can use hardware interrupts and this works with no problem. I tried to implement a solution with gotos in interrupt: procedure just to be reminded that does not work, because you cannot reset the interrupt - this only happens after the return of the interrupt procedure is executed, which is never if you run away from it with a goto. I also tried to do a flag variable that would register what mode am I in - sleeping or running. This is probably the way to go, but my current implementation has two problems: 1) every loop has extra work to do now - check if I'm sleeping or not, which seems a waste of time; 2) handling starting from a just connected battery is not trivial or I cannot figure it out today or both :).

I have a feeling of trying to invent a bicycle here, so I'm asking for help - how would you do it or how have you done it? Just a general idea or workflow for starters.


Thank you for your time,

Edmunds
 
Last edited:

erco

Senior Member
Ha... I'm sorting this out for a 20M2 right now. My huge program is filling the memory, so I still have to crunch here before I can add there. Will advise if & when I split the atom, but I'm sure someone else will chime in first.
 

Flenser

Senior Member
Edmunds,

Based on your description it sounds something like this:

Code:
setup hardware interrupt
sleep_label:
sleep 0
<init code>
do
  <main code>
 <code to detect how long the button is pressed>
  if button pressed > 3 sec then button_press = true endif
loop untl button_press = true
goto sleep_label

interrupt_routine:
<do nothing>
return
Whenever you connect the X2 picaxe to a battery it will sleep until there is a hardware interrupt
The hardware interrupt does nothing but the program continues after the sleep command to run the init code and then do the main loop forever
If the button is pressed for longer than 3 sec the program exits the main loop and jumps back to the sleep command where it will sleep until there is a hardware interrupt
 

inglewoodpete

Senior Member
I use bit variables to indicate the current state of the PICAXE. Events are used to change the state of these variables. Typical events are switch or timer state changes. Eg. 1. If an input changes state (button is pressed) when your timer is inactive, make something happen and enable the timer. 2. If an input changes state while the time is active, make something else happen and/or disable the timer.

Record the state of your program in the bit variables: Eg Timer active, Lights on/off, motor running. Test these states when events occur.
 

hippy

Senior Member
This is actually quite a tricky one. You don't want any polling once running so that necessitates using interrupts, and different interrupts depending on what's gone before, so one doesn't get held up within the interrupt routine.

So you basically need an interrupt driven Finite State Machine which adjusts state when interrupted and selects the next interrupt which will move it to the next state.

My first effort waits for the initial push then starts running, interrupts firstly on that initial push being released, then interrupts on subsequent pushes. On any push it then initiates a 3 second timeout, and when that times-out and interrupts, checks to see if the button is still pushed. If so, it shuts down, otherwise returns to waiting for the next push.

This works okay in simulation -
Code:
#Picaxe 40X2

Symbol  POWER_BUTTON          = pinB.2
#define INTERRUPT_PORT          B
#define INTERRUPT_MASK          %00000100
#define PUSHED                  1

Symbol  reserveW0             = w0 ; b1:b0

Symbol  mode                  = b2
#define MODE_INIT               0
#define MODE_WAIT_FOR_PUSH      1
#define MODE_WAIT_FOR_TIMEOUT   2
#define MODE_SHUTDOWN           3

PowerOnReset:
  Do
    Sleep 1
  Loop Until POWER_BUTTON Is PUSHED
  Gosub Init
  mode = MODE_INIT
  Gosub Interrupt_Enable

MainLoop:
  Do
    ; Do things here
  Loop

Init:
  ; Do things here
  Return

ShutDown:
  ; Do things here
  Return

Interrupt:
  Select Case mode
    Case MODE_INIT
      ; Power button released
      mode = MODE_WAIT_FOR_PUSH
    Case MODE_WAIT_FOR_PUSH
      ; Power button pushed
      mode = MODE_WAIT_FOR_TIMEOUT
    Case MODE_WAIT_FOR_TIMEOUT
      ; Timed-out
      If POWER_BUTTON Is PUSHED Then
        ; Button still held so shutdown
        mode = MODE_SHUTDOWN
      Else
        ; Button released, wait for next push
        mode = MODE_WAIT_FOR_PUSH
      End If
  End Select

Interrupt_Enable:
  ; Initially disable interrupts
  SetInt      OFF
  SetIntFlags OFF
  ; Choose what to actually interrupt on next
  Select Case mode
    Case MODE_INIT
      ; Interrupt when power button released
      b0 = PUSHED
      If b0 = 1 Then
        SetInt 0, INTERRUPT_MASK, INTERRUPT_PORT
      Else
        SetInt INTERRUPT_MASK, INTERRUPT_MASK, INTERRUPT_PORT
      End If
    Case MODE_WAIT_FOR_PUSH
      ; Interrupt when power button pushed
      b0 = PUSHED
      If b0 = 1 Then
        SetInt INTERRUPT_MASK, INTERRUPT_MASK, INTERRUPT_PORT
      Else
        SetInt 0, INTERRUPT_MASK, INTERRUPT_PORT
      End If
    Case MODE_WAIT_FOR_TIMEOUT
      ; Interrupt after 3 second timeout
      SetTimer T1S_8
      timer = -3
      SetIntFlags $80, $80
    Case MODE_SHUTDOWN
      ; Perform a shutdown
      Gosub ShutDown
      ; Reset once we release the button
      Do
        Sleep 1
      Loop While POWER_BUTTON Is PUSHED
      Reset
  End Select
  Return
I am using SETINT in there rather than any HINT pin as it's more general purpose, can be used on other pins.

There is a minor operational issue during the 3 second timeout. If the button is released and pushed again, so long as it is pushed when the timeout occurs, it is treated as if having been pushed continually for 3 seconds. But I don't think that's really much of a problem in practice.

It would be possible to interrupt on timeout or button released, check which it was when that interrupt occurs. But I'm not sure it's really worth it. One would have to use HINT interrupts because SETINT and SETINTFLAGS cannot both be active at the same time.

One could also move to using HINT interrupts throughout rather than SETINT but that could get tricky with brief pushes of the button leading to an edge getting missed while preparing to look for that edge. Level sensitive SETINT rather than edge triggered HINT is actually better in this case.

And the reason for #DEFINE - that's so the INTERRUPT_PORT can be named. And if one constant is a #DEFINE it looks better if all are. Note that, unlike SYMBOL, those don't have an "=" within them.
 

hippy

Senior Member
I'm sorting this out for a 20M2 right now
The same code converted for use with an M2, using a separate task to deliver the timeout functionality -
Code:
#Picaxe 20M2

Symbol  POWER_BUTTON          = pinC.1
#define INTERRUPT_PORT          C
#define INTERRUPT_MASK          %00000010
#define PUSHED                  1

Symbol  reserveW0             = w0 ; b1:b0

Symbol  mode                  = b2
#define MODE_INIT               0
#define MODE_WAIT_FOR_PUSH      1
#define MODE_WAIT_FOR_TIMEOUT   2
#define MODE_SHUTDOWN           3

Symbol  timeout               = b3

PowerOnReset:
  Do
    Sleep 1
  Loop Until POWER_BUTTON Is PUSHED
  Gosub Init
  mode = MODE_INIT
  Gosub Interrupt_Enable

MainLoop:
  Do
    ; Do things here
  Loop

Init:
  ; Do things here
  Return

ShutDown:
  ; Do things here
  Return

Interrupt:
  Select Case mode
    Case MODE_INIT
      ; Power button released
      mode = MODE_WAIT_FOR_PUSH
    Case MODE_WAIT_FOR_PUSH
      ; Power button pushed
      mode = MODE_WAIT_FOR_TIMEOUT
    Case MODE_WAIT_FOR_TIMEOUT
      ; Timed-out
      If POWER_BUTTON Is PUSHED Then
        ; Button still held so shutdown
        mode = MODE_SHUTDOWN
      Else
        ; Button released, wait for next push
        mode = MODE_WAIT_FOR_PUSH
      End If
  End Select

Interrupt_Enable:
  ; Initially disable interrupts
  SetInt      OFF
   ; Choose what to actually interrupt on next
  Select Case mode
    Case MODE_INIT
      ; Interrupt when power button released
      b0 = PUSHED
      If b0 = 1 Then
        SetInt 0, INTERRUPT_MASK, INTERRUPT_PORT
      Else
        SetInt INTERRUPT_MASK, INTERRUPT_MASK, INTERRUPT_PORT
      End If
    Case MODE_WAIT_FOR_PUSH
      ; Interrupt when power button pushed
      b0 = PUSHED
      If b0 = 1 Then
        SetInt INTERRUPT_MASK, INTERRUPT_MASK, INTERRUPT_PORT
      Else
        SetInt 0, INTERRUPT_MASK, INTERRUPT_PORT
      End If
    Case MODE_WAIT_FOR_TIMEOUT
      ; Interrupt after 3 second timeout
      timeout = 3
      resume 1
    Case MODE_SHUTDOWN
      ; Suspend main task
      suspend 0
      ; Perform a shutdown
      Gosub ShutDown
      ; Reset once we release the button
      Do
        Sleep 1
      Loop While POWER_BUTTON Is PUSHED
      Reset
  End Select
  Return

; Timer interrupt task

Start1:
  Do
    Do While timeout > 0
      Pause 1000
      timeout = timeout - 1
      If timeout = 0 Then
        Gosub Interrupt
      End If
    Loop
    Suspend 1
  Loop
If you simulate that it will appear to get hung up in the MainLoop after C.2 is held for 3 seconds, stuck in the DO or LOOP commands, until you release the C.2 button and the RESET occurs. It's all working, just that simulation doesn't show all tasks executing by default.

Edited : Added a "SUSPEND 0" to actually stop the MainLoop from running before Shutdown is called.
 
Last edited:

edmunds

Senior Member
Dear all,

Thank you all for your inputs. I worked for some time more on it last night after posting and I have something like this that works:

Code:
#macro EnterSleepMode()
  low D.7                              'STSPIN220 EN low to save power and reduce heat
  low A.5                              'DRB8837 EN low to save power
  hi2cout %00010000, (0x00,0x00,0x40)  'EN3V3 low to disable 3.30V switcher and save power
  disablebod                           'Disable brown-out detect for extra power savings
  sleep 0                              'Sleep for infinity to save power
#endmacro

{'Bit variables
Symbol sleeping = bit31    'Flag for sleep mode
}

{'Byte variables
Symbol b_counter = b4
Symbol mag_cmd = b5
}

{'Wrod variables
Symbol sum0 = w27            'b54:b53; Additions
Symbol l_error = w26         'b52:b51; Line error
Symbol chg_sense = w25       'b50:b49; Line error
Symbol speed = w24           'b48:b47; Line error
Symbol helper = w23          'b46:b45; Line error
Symbol chg_voltage = w22     'b44:b43; Line error
Symbol LL_ADC = w21          'b42:b41; Leftmost hall sensor reading
Symbol L_ADC = w20           'b40:b39; Left hall sensor reading
Symbol R_ADC = w19           'b38:b37; Right hall sensor reading
Symbol RR_ADC = w18          'b36:b35; Rightmost hall sensor reading
Symbol counter0 = w17        'b34:b33; Just a counter
Symbol counter1 = w16        'b32:b31; Just a counter
Symbol ELine = w15           'b30:b29; Line position error
Symbol PLine = w14           'b28:b27; P term, PID
Symbol DLine = w13           'b26:b25; D term, PID
Symbol Steer_input = w12     'b24:b23; Steer input, number of stp pulses to STSPIN220
Symbol D_error = w11         'b22:b21; Derivative error for D term calculation
Symbol OldELine = w10        'b20:b19; Old line position error
Symbol steer_sense = w9      'b18:b17;
Symbol max_steer_input = w8  'b16:b15; Agregated steering input
Symbol steer_pos = w7
Symbol steer_output = w6

Symbol line_pos = w0         'b0:b1; MagLine position
}

{'I2C addresses
Symbol PCA9551_R = 0xC6      'rear light panel
Symbol TCA6507 = 0x8a        'LED driver/output expander
}

{'Constants
Symbol Steer_pulse = 200 '200
Symbol steer_limit_left = 20     'ADC value with magnet centered on steering position sensor
Symbol steer_max = 380           'Could be the number of allowed steps from centre in one direction
Symbol steer_limit = 220'210
Symbol min_spread = 20 '20       'low treshold for considering adjacent hall sensors equal
Symbol max_spread = 35 '35       'max difference of adjacent hall sensors
Symbol KpELine = 26    '26       'P coefficient/multiplier for line position error correction
Symbol KdEline = 900   '900      'D coefficient for line error corrections
Symbol InputDiv = 30   '30       'Input divider to go down to reasonable value for number of steps for the motor
}

{'FREQ dependent constants
'Symbol I2CSpeed = i2cfast
Symbol I2CSpeed = i2cfast_64
}

{'PIC registers
Symbol PMD0     = $3F
Symbol PMD1     = $3E
Symbol PMD2     = $3D

Symbol TRIS_E   = $96
Symbol TRIS_D   = $95
Symbol TRIS_C   = $94        'have to use underscore, TRISC variable taken by firmware
Symbol TRIS_B   = $93
Symbol TRIS_A   = $92

Symbol T1CON    = $CD
Symbol T2CON    = $BA
Symbol T4CON    = $51
Symbol T6CON    = $4A

Symbol TMR4     = $53
Symbol TMR5H    = $50
Symbol TMR5L    = $4F
Symbol T5CON    = $4E
Symbol T5GCON   = $4D

Symbol PR2      = $BB
Symbol PR4      = $52
Symbol PR6      = $4B

Symbol PIR1     = $9E
Symbol PIR2     = $A1
Symbol PIR3     = $A4
Symbol PIR4     = $7B
Symbol PIR5     = $7E
Symbol PIE1     = $9D
Symbol PIE2     = $A0
Symbol PIE3     = $A3
Symbol PIE4     = $7A
Symbol PIE5     = $7D

Symbol CCPR1H   = $BF
Symbol CCPR1L   = $BE
Symbol CCPR3H   = $5F
Symbol CCPR3L   = $5E
Symbol CCP3CON  = $5D
Symbol CCP1CON  = $BD
Symbol PWM1CON  = $B7
Symbol PWM3CON  = $5C
Symbol ECCP1AS  = $B6
Symbol PSTR1CON = $B9
Symbol ECCP3AS  = $5B
Symbol PSTR3CON = $5A
Symbol CCPR5H   = $56
Symbol CCPR5L   = $55
Symbol CCP5CON  = $54
Symbol CCP2CON  = $66
Symbol PSTR2CON = $63

Symbol CCPTMRS0 = $49
Symbol CCPTMRS1 = $48
}

  setfreq em64                      'Set frequency to 64MHz after exiting sleep mode
 
  hintsetup %00000100               'Set up hardware interrupts for ON/OFF button
  setintflags %00000100, %00000100  'Set up flags for hardware interrupts

  hi2csetup i2cmaster, PCA9551_R, I2CSpeed, i2cbyte  'Set up I2C comms

  EnterSleepMode                    'Run sleep mode macro

main:
  do
    if sleeping = 0 then
      EnterSleepMode                'Run sleep mode macro
    else
      gosub Init
      do
        gosub MagLine
        if No_magnet = 0 then
          GetSignedPLine                         'Calculate P term
          d_error = ELine - OldELine             'Calculate error trend (derivative)
          GetSignedDLine                         'Calculate D term
          OldELine = ELine                       'Set the old error the current error
          GetLimitedSteerInput
          RunLimitedSteeringStepper
        endif
      ;  gosub CheckForCharging
      loop while sleeping <> 0
    endif
  loop

init:
  enablebod                                          'Enable brown-out detect
  hi2csetup i2cmaster, PCA9551_R, I2CSpeed, i2cbyte  'Set up I2C comms

'Initialise STSPIN220
  high EN
  hi2cout [TCA6507], %00010000, (0x00,0x00,0x10)  'STBY low
'  high MODE1                          'set high by pull-up
' low MODE2                            'connected to GND on PCB
  low DIR
  high STCK
  pause 100
  hi2cout %00010000, (0x00,0x00,0x00)  'STBY set high by pullup to register state of mode pins
  low STCK
  low EN

 
  InitSteering
sertxd ("out of init",CR,LF)
  high A.5                        'take DRV8837 out of sleep
'Initialise PWM, CCP3 on B.5, multiplexed by picaxe firmware /Main motor/
  pwmout pwmdiv16, B.5, 249, 499  'period and duty values irrelevant as switching timer away from default timer4
  input B.5 : input A.6           'make PWM pins inputs during configuration
  pokesfr PR6,     %00011111      'set timer6 period
'max speed for coil-finding
'  pokesfr CCP3CON, %10111100      'configure half-bridge mode, active high polarity for both pins
'  pokesfr CCPR3L,  %00011010      'The rest of the duty cycle
'crawl speed
  pokesfr CCP3CON, %10111100      'configure half-bridge mode, active high polarity for both pins
  pokesfr CCPR3L,  %01100111      'The rest of the duty cycle
'stop speed
'  pokesfr CCP3CON, %10111100      'configure half-bridge mode, active high polarity for both pins
'  pokesfr CCPR3L,  %00001111      'The rest of the duty cycle
'magline chasing speed
'  pokesfr CCP3CON, %10111100      'configure half-bridge mode, active high polarity for both pins
'  pokesfr CCPR3L,  %01011111      'The rest of the duty cycle
 
  pokesfr PWM3CON, %10001010      'PRSEN bit set, but should not matter, dead-band delay set to 10
  output B.5 : output A.6         'start PWM by enabling A and B pins as outputs
return

interrupt:
  if pinPWRSW = 0 then interrupt
  if hint2flag = 1 then                'if this is coming from the power switch...
    if sleeping = 0 then               'was sleeping
      sleeping = 1                     'set sleeping flag to running state [1]
      hint2flag = 0 : hintflag = 0     'reset interrupt flags
    else                               'was not sleeping
      sleeping = 0                     'set sleeping flag to sleeping state [0]
      hint2flag = 0 : hintflag = 0     'reset interrupt flags
    endif
  endif
  setintflags %00000100, %00000100     'enable interrupt flags
return
I tried to post everything that is related - I hope I did not miss anything. The entire program is too big for posting.

I also tried to measure current consumption in sleep mode and it seems to be under 1uA, because I have a multimeter with uA range and it shows 0.00 for sleep mode and expected consumption when running (so I know it is working).

@hippy, I think your code is so universally written, it deserves a place in code snippets section :).

Added: I dropped a three second requirement because I arrived at the same problem @hippy is describing and thought it was not worth the trouble at this point.

Thank you once again,

Edmunds
 
Last edited:
Top