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

AIS Alarm - The Process

AIS Alarm - Prototype Hardware

AIS Alarm - Timers and GPIO

AIS Alarm - Prototype Code Outline

AIS Alarm - UART

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

TI MSP430FR2111