Lecture 10 - Debouncing ----------------------- 10:10 Debouncing Today we discuss an application of FSM + Timer, described using a cyclic executive. Let's start with a small experiment. - Attach an oscilloscope probe to button S1 on the sensor board. This button is available on connector J4.33 - Set the time base to 5ms per division horizontal, 1v per division vertical - Trigger on the up or down going edge with a trigger level of ~2v Now, press button S1 and watch what happens. There are many up- and down-edges that are not a clean 'button press' event. This phenomenon is causes by mechanical effects within the button itself, causing repeated making and breaking of a contact. The period of switch bouncing can range, depending on the type of button, from less than a millisecond to tens of milliseconds. This causes a lot of trouble, of course. Assume that you have written a program such as the following while (1) { prev_b = b; b = ButtonS1Pressed(); if (bd & !prev_b) ColorLEDToggle(); } Because there can be several 0->1 transitions for a single button press, each of them a few milliseconds apart, the ColorLED could toggle multiple times. That is not what the programmer had intended. Instead, the idea of this program is: 1 button press = 1 toggle. The button bouncing is picked up by the software cyclic executive because the loop spins so quickly, that it can see all of the button transitions. Is this a realistic assumption? Yes indeed. Assume that the loop body needs a 50 instructions (a generous amount for such a simple program), then the while loop would spin at 3MHz/50 = 60KHz, or once every 17 microseconds. Since the bounces take multiple milliseconds, all of these up- and down transitions will be picked up by Button1Pressed(). 10:25 Debouncing The solution for bouncing is debouncing - ensuring that a switch is stable before it is read. An easy algorithm is the following: - Assume that a button is in a stable state, S - We are looking for changes of the button (the button state becomes !S) - When you see such a change, start a timer - If the button changes state again before the timer reaches a timeout, ignore the first change and maintain state S - If the button does NOT change state again before the timer expires, change S into !S: new stable state reached The timeout value is the debounce time, typically 10ms or more. It is a value longer than the longest bounce event. Example: Pressed Stable Here | | V V +-+ +-+ +-------------------- | | | | | ----+ +--+ +--+ At the start, the button is 0, which is the stable state. S=0. This button has two bounces before reaching the new stable state. To debounce it, we will need a debounce time longer than the bouncing event. Let's say the debounce time is like this: |<-------------->| That is wide enough to mask out all bounces. When button->1, we start a timer. +-+ +-+ +-------------------- | | | | | ----+ +--+ +--+ | start timer expected timeout |<-------------->| However, the button is zero again (bounce effect) before reaching timeout. So we cancel the timer and maintain the same stable state S=0 +-+ +-+ +-------------------- | | | | | ----+ +--+ +--+ | | |<------xxx--> | cancel timer We wait a bit longer, and at the next button->, we start the timer again. +-+ +-+ +-------------------- | | | | | ----+ +--+ +--+ | start timer expected timeout |<-------------->| We will also cancel this timer, because the button falls to 0 +-+ +-+ +-------------------- | | | | | ----+ +--+ +--+ | | |<------xxx----->| | cancel Finally, we start the timer again at the following transition +-+ +-+ +-------------------- | | | | | ----+ +--+ +--+ | start timer expected timeout |<-------------->| In this case, we reach timeout before the button changes state. We define this as the new stable state, S=1. So, the software value of the button is as follows: Physical button state +-+ +-+ +-------------------- | | | | | ----+ +--+ +--+ debounce time |<-------------->| S 0000000000000000000000000000000|1111111111 You can make a similar argument for 1->0 transitions. We will program a debouncer that handles both 0->1 and 1->0 bounces. 10:35 The One-Shot Timer We need a timer to implement the debouncing program, but it's not a periodic timer as discussed previously. A timer that is started by the programmer and detected for completion is called a ONE-SHOT timer. You may recall the ARM Timer32. There are two 32-bit timers in the ARM processor, and they can both be operated as periodic timers or as one-shot timers. The hardware construction of the timer is as follows: Clock ---> Prescaler ---> Periodic Counter ->> Timer32_getValue() ^ ^ | | prescaler_value one_shot_mode + period The 3 MHz processor clock is divided by a 'prescaler', a hardware module that reduces the clock rate by a factor of 1, 16 or 256. The Periodic Counter is programmed with a Period value, which will make the counter count down from Period to 0 every time it is reset. If the Periodic Counter is in 'one-shot' mode, the counter will count down to 0 and stay at that value. If the Period Counter is not in 'one-shot' mode, it counter will wrap-around to the Period value when it reaches 0. So to use the timer as a One-Shot timer, we would use the following functions: void InitTimer() { Timer32_initModule(TIMER32_0_BASE, TIMER32_PRESCALER_1, TIMER32_32BIT, TIMER32_PERIODIC_MODE); } void TimerStartOneShot() { // 100ms second period on 3MHz clock Timer32_setCount(TIMER32_0_BASE, 300000); Timer32_startTimer(TIMER32_0_BASE, true); } int TimerExpiredOneShot() { return (Timer32_getValue(TIMER32_0_BASE) == 0); } In this code, - TIMER32_0_BASE selects the timer32 counter (there are two of them in the ARM, TIMER32_0_BASE and TIMER32_1_BASE) - TIMER32_PRESCALER_1 sets the prescaler to 1 - TIMER32_32BIT sets the counter in 32-bit mode (16-bit is also possible) - TIMER32_PERIODIC_MODE sets the counter as periodic counter (otherwise it counts from 2^16 or 2^32 down to 0). 10:50 FSM Design The debouncing logic above can be coded in a finite state machine. The input of the FSM is the physical button position (with bounces) The output of the FSM is the debounced button We will use four states. Two states will be used to represent 'stable button' states. Two other states are transition states, that verify when we reach the timeout value. Let's start with a pseudocode description FSM input: b (button value) state stable0: if (b) start-the-timer goto state trans0 state trans0: if (b and timer-timerout) goto state stable1 if (!b) goto state stable0 state stable1: if (!b) start-the-timer goto state trans1 state trans1: if (!b && timer-timeout) goto state stable0 if (b) goto state stable1 return value: if (state == (stable0 or trans0)) then S=0 else S=1 This FSM can be called from within the cyclic executive. Each iteration of the cyclic executive feeds in a new button value into the FSM. The FSM controls the timer, and computes the stable button state. And of course, the debounce time must be programmed as the timer period. Putting everything together (see the example msp432-button-debounce: bool BounceFSM(bool b) { typedef enum {stable0, trans0, stable1, trans1} state_t; static state_t S = stable0; bool rval; switch (S) { case stable0: rval = false; if (b) { TimerStartOneShot(); S = trans0; } break; case trans0: rval = false; if (b && TimerExpiredOneShot()) S = stable1; if (!b) S = stable0; break; case stable1: if (!b) { TimerStartOneShot(); S = trans1; } rval = true; break; case trans1: if (!b && TimerExpiredOneShot()) S = stable0; if (b) S = stable1; rval = true; break; } return rval; } #define UPEDGE(A, B) (A && !(B)) int main(void) { bool b = false, prev_b; bool bd = false, prev_bd; WDT_A_hold(WDT_A_BASE); InitButtonS1(); InitLEDs(); while (1) { prev_b = b; prev_bd = bd; b = ButtonS1Pressed(); bd = BounceFSM(b); if (bd & !prev_b) MainColorLEDToggle(); if (bd & !prev_bd) SensorColorLEDToggle(); } } 10:59 Conclusions - Debouncing requires a time delay and an FSM to implement debouncing logic - Time delays can be created with a one-shot timer