Project Objective: Develop an open source AIS Alarm that alerts sailors that a new marine vessel with AIS is within range
The prototype now receives AIS messages from the dAISy module and stores them in a persistent FRAM ring buffer. My intention today was to test the ring buffer but I discovered lots of bounce with the button used to push the buffer contents to UART. This post will outline a software debounce scheme that makes use of the millisecond counting ISR already in the code.
The following code is stripped down to only what is necessary to develop and test. It is based in part on an article in Hackaday by Elliot Williams which in turn references an article by Jack Ganssle.
/* * dAISy_Alarm_V001a - adds debounced momentary button switch * dAISy_Alarm_V001 - implements millisecond timer and blinks LED * * MCLK, SCLK set to 8 MHz * Milliseconds counted with timer B0 ISR * Button state tracked in timer B0 ISR * * Frank Milburn 2 January 2018 * Developed on MSP430FR2111 with CCS V7.2, compiler TIv17.9.0.STS */ #include "driverlib.h" // Clock and Timer related definitions #define TIMER_UP_COUNT 8000 // approximately 1 millisecond @ 8 MHz // GPIO related definitions #define LED GPIO_PORT_P1,GPIO_PIN0 // LED on P1.0 #define BUTTON GPIO_PORT_P1,GPIO_PIN2 // Momentary switch on P1.2 // Function prototypes void initClocks(void); // initialize clocks void initGPIO(void); // initialize GPIO void initTimerB0(void); // initialize timer B0 unsigned long milliSecs(void); // returns milliseconds since counter started // Global variable declarations volatile unsigned long MILLIS = 0; // running count of milliseconds const unsigned int PUSHED = 0x0000; // pushed button is low (debounced history) const unsigned int NOT_PUSHED = 0xFFFF; // open button is high (debounced history) volatile unsigned int BUTTON_HISTORY = 1; // stores button state history int main(void) { WDT_A_hold(WDT_A_BASE); // stop the watch dog PMM_unlockLPM5(); // activate port settings after power on initClocks(); initGPIO(); initTimerB0(); __bis_SR_register(GIE); // globally enable interrupts unsigned int buttonState = NOT_PUSHED; // initialize button states unsigned int lastButtonState = NOT_PUSHED; for(;;){ buttonState = BUTTON_HISTORY; // store button history since it can be changed in ISR if (buttonState != lastButtonState) { // check if the state has changed if (buttonState == PUSHED){ // debounced pushed GPIO_toggleOutputOnPin(LED); lastButtonState = PUSHED; } else if (buttonState == NOT_PUSHED) { // debounced open lastButtonState = NOT_PUSHED; } } } } // ------------------------------------------------------------------------------------- void initClocks(void){ __bis_SR_register(SCG0); // disable FLL CSCTL3 |= SELREF__REFOCLK; // Set REFO as FLL reference source CSCTL1 = DCOFTRIMEN_1 | DCOFTRIM0 | DCOFTRIM1 | DCORSEL_3; // DCOFTRIM=3, DCO Range = 8MHz CSCTL2 = FLLD_0 + 243; // DCODIV = 8MHz __delay_cycles(3); __bic_SR_register(SCG0); // enable FLL CSCTL4 = SELMS__DCOCLKDIV | SELA__REFOCLK; // set default REFO(~32768Hz) as // ACLK source, ACLK = 32768Hz // default DCODIV as MCLK, SMCLK source } // ------------------------------------------------------------------------------------- void initGPIO(void){ GPIO_setAsOutputPin(LED); GPIO_setOutputLowOnPin(LED); GPIO_setAsInputPinWithPullUpResistor(BUTTON); } // ------------------------------------------------------------------------------------- unsigned long milliSecs(void){ return MILLIS; // return milliseconds } // ------------------------------------------------------------------------------------- void initTimerB0(void){ TB0CCTL0 |= CCIE; // TBCCR0 interrupt enabled TB0CCR0 = TIMER_UP_COUNT; // approx 1 millisecond TB0CTL |= TBSSEL__SMCLK | MC__UP; // SMCLK, Up mode } // Timer B0 interrupt service routine #if defined(__TI_COMPILER_VERSION__) || defined(__IAR_SYSTEMS_ICC__) #pragma vector = TIMER0_B0_VECTOR __interrupt void Timer_B (void) #elif defined(__GNUC__) void __attribute__ ((interrupt(TIMER0_B0_VECTOR))) Timer_B (void) #else #error Compiler not supported! #endif { MILLIS++; BUTTON_HISTORY = BUTTON_HISTORY << 1; BUTTON_HISTORY |= GPIO_getInputPinValue(BUTTON); }
Here are the important additions to previous code:
- Line 17 defines the register location of the button
- Line 25 is a convenient constant for comparison when the button is pushed. It is 0 because the switch is held high normally but goes to ground when the button is pushed. It will be described in more detail along with lines 26 and 27 below.
- Line 26 is a convenient constant for comparison when the button is not pushed.
- Line 27 is a volatile variable which keeps track of the button's history (pushed / not pushed) and is updated in the millisecond ISR.
- Lines 35-48 manage the button in main() and are straight forward. The last button state is tracked to see if it changes. If it does, appropriate action is taken depending on whether the button is pushed or not. Note that since BUTTON_HISTORY is volatile the contents are placed into buttonState to avoid a race condition in the event the ISR is triggered midway.
- Line 67 sets the registers for the button to input and pulled up
- Lines 90 and 91 are the clever part. Every millisecond the ISR is triggered and the bits in BUTTON_HISTORY are shifted left by one bit making room for the latest button state. Then the contents of the register containing the current button state is read and a logic OR used to insert it along side the previous 15 bits.
Let's go back up to line 39 again. It checks to see if the button state has changed since the last pass through. If so, line 40 will check to see if the button has been pushed. But to be pushed, the contents must be 0 which means that the last 16 interrupts in the Timer B0 ISR recorded the history as pushed with no bounce. Since the ISR updates every millisecond, this means the switch has been steady closed for at least 16 milliseconds with no bounce. In a similar manner, line 44 will determine if the switch has been steady open for at least 16 milliseconds.
So what happens if it is neither fully closed or fully open (i.e. changing state or bouncing)? Nothing, the LED remains in its previous state. Of course the next time through it will probably see a change in button state since it was caught during a state change or bouncing. Eventually it should find a steady open or closed position. I don't foresee this being a problem in this project but different action could be taken during a transition or bounce if desired.
This is the first time I've used this approach having previously used hardware solutions and a wait until delay is complete approach. As usual, all comments and suggestions are appreciated. I would be interested if you see a problem with the code above or have had success with similar approaches.
Past Posts from this Project:
AIS Alarm - Prototype Hardware
AIS Alarm - Prototype Code Outline
AIS Alarm - First AIS Messages
AIS Alarm - First FRAM Storage
References and Links:
WEGMATT LLC - dAISy AIS Receiver - low cost AIS receiver
Texas Instruments MSP430FR2xx FRAM Microcontrollers - Post No. 4