ledsw1_cn.c - Example of implementing a FSM using a timer and change notification interrupt.

Demonstrates the use of a events to create an energy-efficient FSM implementation. All of the FSM work is done in the ISR.

 
 
#include <stdio.h>
#include "pic24_all.h"

void update_state(void);
 

Configuration

LED1 configuration and access

#define CONFIG_LED1() CONFIG_RB14_AS_DIG_OUTPUT()
#define LED1 (_LATB14)
 

Pushbutton configuration and access

void config_pb()  {
  CONFIG_RB13_AS_DIG_INPUT();
  ENABLE_RB13_PULLUP();

Give the pullup some time to take effect.

  DELAY_US(1);
}

#if (HARDWARE_PLATFORM == EMBEDDED_C1)
# define PB_PRESSED()   (_RB7 == 0)
# define PB_RELEASED()  (_RB7 == 1)
#else
# define PB_PRESSED()   (_RB13 == 0)
# define PB_RELEASED()  (_RB13 == 1)
#endif
 

Switch configuration and access

void config_sw()  {
  CONFIG_RB12_AS_DIG_INPUT();
  ENABLE_RB12_PULLUP();

Give the pullup some time to take effect.

  DELAY_US(1);
}

#define SW (_RB12)
 

Change notification interrupt configuration

Enable change-notification interrupts on the pushbutton.

void config_cn(void) {

Enable change notifications on RB13 specifically.

  ENABLE_RB13_CN_INTERRUPT();

Clear the interrupt flag.

  _CNIF = 0;

Choose a priority.

  _CNIP = 1;

Enable the Change Notification general interrupt.

  _CNIE = 1;
}
 

Timer3 interrupt configuration

Configure the timer to produce interrupts. Do not yet turn it on.

void configTimer3(void) {

Ensure that Timer2,3 configured as separate timers by turning 32-bit mode off.

  T2CONbits.T32 = 0;

T3CON set like this for documentation purposes. This could be replaced by T3CON = 0x0020.

  T3CON = T3_OFF | T3_IDLE_CON | T3_GATE_OFF
          | T3_SOURCE_INT
          | T3_PS_1_256;

Clear the interrupt flag.

  _T3IF = 0;

Choose a priority.

  _T3IP = 1;

Enable the timer3 interrupt.

  _T3IE = 1;
}
 
 

Interrupts

Timer arming

This function prepares the timer to interrupt after the given delay (in ms).

void timer3_arm(uint16_t u16_time_ms) {

Convert arm time to timer3 ticks.

  PR3 = msToU16Ticks(u16_time_ms, getTimerPrescale(T3CONbits)) - 1;

Zero then start the timer.

  TMR3 = 0;
  T3CONbits.TON = 1;

If a timer interrupt has occurred but not been processed, discard it.

  _T3IF = 0;
}
 

Change notification

Interrupt service routine for change notification interrupts. Disable this interrupt to avoid bounce, then arm the timer to update the FSM state after a debounce delay.

void _ISR _CNInterrupt(void) {

Acknowledge the interrupt, then disable it. Otherwise, any switch bounce would cause spurious interrupts.

  _CNIF = 0;
  _CNIE = 0;

Schedule a timer interrupt after a debounce delay.

  timer3_arm(DEBOUNCE_DLY);
}
 

Timer

Interrupt Service Routine for Timer3

void _ISR _T3Interrupt(void) {

Clear the interrupt flag.

  _T3IF = 0;

Stop the timer; the debounce delay is done.

  T3CONbits.TON = 0;

Clear the change notification interrupt because switch bounce may have set it.

  _CNIF = 0;

Re-enable change notification interrupts, since the debounce delay is done.

  _CNIE = 1;

Run our state machine.

  update_state();
}
 

State machine

First, define the states, along with a human-readable version.

typedef enum  {
  STATE_RELEASED1,
  STATE_PRESSED1,
  STATE_RELEASED2,
  STATE_PRESSED2,
  STATE_RELEASED3_BLINK,
  STATE_PRESSED3,
} state_t;

const char* apsz_state_names[] = {
  "STATE_RELEASED1 - LED is off",
  "STATE_PRESSED1",
  "STATE_RELEASED2 - LED is on",
  "STATE_PRESSED2 - SW2 on goes to blink else go to RELEASED1",
  "STATE_RELEASED3_BLINK - LED blinks, waiting for SW1 press",
  "STATE_PRESSED3 - LED is on",
};
 

Provide a convenient function to print out the state.

void print_state(state_t e_state) {

Verify that the state has a string representation before printing it.

  ASSERT(e_state <= N_ELEMENTS(apsz_state_names));
  outString(apsz_state_names[e_state]);
  outChar('\n');
}
 

This function defines the state machine.

void update_state(void) {
  static state_t e_state = STATE_RELEASED1;

The number of times the LED was toggled in the blink state

  static uint16_t u16_led_toggles;

  switch (e_state) {
    case STATE_RELEASED1:
      LED1 = 0;
      if (PB_PRESSED()) {
        e_state = STATE_PRESSED1;
      }
      break;

    case STATE_PRESSED1:
      if (PB_RELEASED()) {
        e_state = STATE_RELEASED2;
      }
      break;

    case STATE_RELEASED2:
      LED1 = 1;
      if (PB_PRESSED()) {
        e_state = STATE_PRESSED2;
      }
      break;

    case STATE_PRESSED2:
      if (PB_RELEASED() && SW) {
        e_state = STATE_RELEASED3_BLINK;

Zero the toggled count when entering the blink state.

        u16_led_toggles = 0;

Schedule a timer interrupt to start the blinking.

        timer3_arm(250);
      }
      if (PB_RELEASED() && !SW) {
        e_state = STATE_RELEASED1;
      }
      break;

    case STATE_RELEASED3_BLINK:

Toggle the LED.

      LED1 = !LED1;
      u16_led_toggles++;
      printf("toggles = %d\n", u16_led_toggles);

Schedule a timer interrupt to start the blinking.

      timer3_arm(250);

      if (u16_led_toggles >= 10) {
        e_state = STATE_RELEASED1;
      }
      if (PB_PRESSED()) {
        e_state = STATE_PRESSED3;
      }
      break;

    case STATE_PRESSED3:
      LED1 = 1;
      if (PB_RELEASED()) {
        e_state = STATE_RELEASED1;
      }
      break;

    default:
      ASSERT(0);
  }

  print_state(e_state);
}
 
 

Main

int main(void) {
  configBasic(HELLO_MSG);
  config_pb();
  config_sw();
  CONFIG_LED1();
  configTimer3();
  config_cn();

  while (1) {

Enter a low-power state, which still keeps timer3 and uart1 running.

    IDLE();
  }
}