Understanding "void (*state)() = NULL;"

Hi folks,

The project required a menu driven LCD display to control 4 motors, so I used Hunt The Wumpus as a code base. The project is working well, so doesn't require debugging, but if there was a better way of doing things, please let me know!

To give a little background on the project: it's used to control 4x 12V/30A 2000LB ATV ($45 each, super cheap!) winches that are mounted above my garage door to lift my kayaks up and down for storage. I needed to add additional logic to sense wether the garage door was up or down. I didn't want someone playing with it and putting down a kayak while the garage door was up.

This is my first time using C++ or an Arduino and was hoping to get a better understanding of what this particular void is doing; the sketch does not compile without it.

void (*state)() = NULL;
void (*last_state)() = NULL;

Complete Sketch:

// -------------------------------------------------------------------------------
// An Arduino sketch that controls 4 hoists connected to the Arduino Digital PINs:
// Each hoist has a H bridge motor controller attached to it with an ENABLE PIN 
// as well as UP and DOWN PINs.  All hoists share the UP and DOWN PINs and are
// selected by enabling a specific hoist.
//
//
//
// PIN  6 - Garage Door 2
// PIN  7 - Garage Door 1
// PIN  8 - Hoist 1 Enable
// PIN  9 - Hoist 2 Enable 
// PIN 10 - Hoist 3 Enable
// PIN 11 - Hoist 4 Enable 
// PIN 12 - UP
// PIN 13 - DOWN
// --------------------------------------------------------------------------------
#include <Wire.h>
#include <Adafruit_MCP23017.h>
#include <Adafruit_RGBLCDShield.h>

const int sleep_time = 20000;
const int hoist_up = 13;
const int hoist_down = 12;
const int hoist[] = {8, 9, 10, 11};
const int door[] = {7, 6};


void (*state)() = NULL;
void (*last_state)() = NULL;
unsigned long last_state_change_time;
unsigned long time;
const uint8_t menu_col[4][2] = { {0,  3},
                                 {4,  7},
                                 {8,  11},
                                 {12, 15} };

uint8_t selected_menu_idx;
Adafruit_RGBLCDShield lcd = Adafruit_RGBLCDShield();
enum BackLightColor { OFF=0x0, RED=0x1, YELLOW=0x3, GREEN=0x2, TEAL=0x6, BLUE=0x4, VIOLET=0x5, WHITE=0x7 };
uint8_t clicked_buttons;
boolean locked;

void setup() {
  Serial.begin(9600);
  delay(2000);
  
  pinMode(hoist_up, OUTPUT);
  pinMode(hoist_down, OUTPUT);
  pinMode(hoist[0], OUTPUT);
  pinMode(hoist[1], OUTPUT);
  pinMode(hoist[2], OUTPUT);
  pinMode(hoist[3], OUTPUT);
  pinMode(door[0], INPUT);
  pinMode(door[1], INPUT);
  
  lcd.begin(16, 2);
  state = begin_welcome;
  selected_menu_idx = 0;
  locked=false;
  
}

void loop() {
  time = millis();
  
  if (last_state != state) {
    last_state = state;
    last_state_change_time = time;
  }
  check_sleep();
  check_door();
  read_button_clicks();
  state();
  delay(10);
}

void check_sleep() {
  static unsigned long last_button_time;
  static boolean asleep = false;
  
  if ( clicked_buttons ) {
    last_button_time = time;
  }
  if ( time - last_button_time >= sleep_time ) {
    lcd.clear();
    lcd.setBacklight(OFF);
    asleep = true;
    digitalWrite(hoist[0], LOW);
    digitalWrite(hoist[1], LOW);
    digitalWrite(hoist[2], LOW);
    digitalWrite(hoist[3], LOW);
    digitalWrite(hoist_up, LOW);
    digitalWrite(hoist_down, LOW);
    state = check_sleep;
  } else if ( (time - last_button_time < sleep_time ) & (asleep == true) ) {
    asleep = false;
    state = begin_welcome;
  }
}


void check_door() {

  if ( ( digitalRead(door[0]) == LOW) & (locked == true) ) {
      state = blink_close;
  } else if ( ( digitalRead(door[0]) == LOW) & (locked == false) )  {
      digitalWrite(hoist[0], LOW);
      digitalWrite(hoist[1], LOW);
      digitalWrite(hoist[2], LOW);
      digitalWrite(hoist[3], LOW);
      digitalWrite(hoist_up, LOW);
      digitalWrite(hoist_down, LOW);
      lcd.clear();
      lcd.setBacklight(RED);
      lcd.setCursor(0, 0);
      lcd.print(F(" Garage Door Up"));
      locked = true;
      state = blink_close;
  } else if ( ( digitalRead(door[0]) == HIGH) & (locked == true) ) {
    locked = false;
    state = begin_welcome;
  }
}

void blink_close() {
  static boolean blink = true;
  static unsigned long last_blink_time;
  
  if (time - last_blink_time >= 1000) {
    lcd.setCursor(1, 1);
    if (blink) {
      lcd.write(0x7E);
      lcd.print(F(" CLOSE DOOR "));
      lcd.write(0x7F);
    } else {
      lcd.print(F("                "));
    }
    blink = !blink;
    last_blink_time = time;
  }
}  

void read_button_clicks() {
  static uint8_t last_buttons = 0;
  uint8_t buttons = lcd.readButtons();
  clicked_buttons = (last_buttons ^ buttons) & (~buttons);
  last_buttons = buttons;

}

void clear_current_menu() {
  lcd.setCursor(menu_col[selected_menu_idx][0], 1);
  lcd.print(' ');
  lcd.setCursor(menu_col[selected_menu_idx][1], 1);
  lcd.print(' ');
}

void highlight_current_menu() {
  lcd.setCursor(menu_col[selected_menu_idx][0], 1);
  lcd.write(0x7E);
  lcd.setCursor(menu_col[selected_menu_idx][1], 1);
  lcd.write(0x7F);
}

//! Check for left and right button clicks and update the menu index as needed.
void handle_menu() {
  if (clicked_buttons & BUTTON_LEFT) {
    selected_menu_idx = (selected_menu_idx > 0) ? selected_menu_idx - 1 : 3;
  } else if (clicked_buttons & BUTTON_RIGHT) {
    selected_menu_idx = (selected_menu_idx < 3) ? selected_menu_idx + 1 : 0;
  } 
}


void begin_welcome() {
  lcd.clear();
  lcd.setBacklight(TEAL);
  lcd.print(F("Hoist Controller"));
  state = blink_select;
}

void blink_select() {
  static boolean blink = true;
  static unsigned long last_blink_time;
  
  if (time - last_blink_time >= 1000) {
    lcd.setCursor(0, 1);
    if (blink) {
      lcd.write(0x7E);
      lcd.print(F(" PRESS SELECT "));
      lcd.write(0x7F);
    } else {
      lcd.print(F("                "));
    }
    blink = !blink;
    last_blink_time = time;
  }
  
  if (clicked_buttons & BUTTON_SELECT) {
    state = start_menu;
  }
}

void start_menu() {
  digitalWrite(hoist[0], LOW);
  digitalWrite(hoist[1], LOW);
  digitalWrite(hoist[2], LOW);
  digitalWrite(hoist[3], LOW);
  lcd.clear();
  lcd.setBacklight(TEAL);
  lcd.setCursor(0, 0);
  lcd.print(F("Select Hoist #"));
  lcd.setCursor(0, 1);
  lcd.print(F(" 01  02  03  04"));
//  selected_menu_idx = 0;
  lcd.setCursor(menu_col[selected_menu_idx][0], 1);
  lcd.write(0x7E);
//  lcd.print('[');
  lcd.setCursor(menu_col[selected_menu_idx][1], 1);
//  lcd.print(']');
  lcd.write(0x7F);
  state = begin_input_move;
}

void begin_input_move() {
  lcd.setCursor(0, 0);
  lcd.print(F("Select Hoist # "));
  lcd.print(' ');
  state = input_move;
}

void input_move() {
  if (clicked_buttons) {
    clear_current_menu();
    if (clicked_buttons & BUTTON_SELECT) {
        Serial.println("Goint to move_hoist from input_move");
        state = begin_move_hoist;
    } else {
      handle_menu();
    }
    highlight_current_menu();
  }
}

void begin_move_hoist() {
  // Enable Selected Hoist
  digitalWrite(hoist[selected_menu_idx], HIGH);
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print(F("Press UP or DOWN"));
  lcd.setCursor(0, 1);
  lcd.print(F("To Move Hoist"));
  state = move_hoist;
}
  
void move_hoist() { 
  uint8_t buttons = lcd.readButtons();

    if ( clicked_buttons ) {
      if ( clicked_buttons & BUTTON_SELECT) {
        state = start_menu;
      } else 
        digitalWrite(hoist_up, LOW);
        digitalWrite(hoist_down, LOW);
    }

    if (buttons & BUTTON_UP) {
      digitalWrite(hoist_down, LOW);
      digitalWrite(hoist_up, HIGH);
    } else if ( buttons & BUTTON_DOWN) {
      digitalWrite(hoist_up, LOW);
      digitalWrite(hoist_down, HIGH);
    }  
}

These are function pointer declarations. Read more here:

You can use Purdum's Right-Left Rule to figure out complex data definitions in C:

http://jdurrett.ba.ttu.edu/3345/handouts/RL-rule.html

The sketch implements a state machine using the function pointer "state" to track the state. state points to one of the functions,

begin_input_move()
input_move()
begin_move_hoist()
move_hoist()
blink_select()
start_menu()

and others. These functions contain the logic to be processed in that state and they perform a state transition by re-assigning state to point to the next state's function. The main loop runs the state machine by simply calling (*state)() repeatedly.

It's kind of clever, and the O/P, being a first time C user might have some trouble with it.

OK, so it's similar to the way TCL operates with it's state machine. I thought it had to be responsible for the state changes, however I didn't know for sure how it was doing it. I'd never seen a void(*foo)() before nor having it equal something.

What is the difference between this and just calling a void (); directly? I.e could state be a string instead and just set the value of state = begin_input_move();

Sure, but then you need a switch/case or something to determine the value of the string each time through the loop. With the function pointer you only need the switch/case when the state changes.

Why would you need a switch/case to determine the value? Can you:

string state = "begin()";

void loop() {
   state;
}

void begin() {
   state = "end()";
}

void end() {
   state = "begin()"
}

Or is that the whole point behind a pointer function?

Thanks again, I'm sure this is probably trivial for you folks.

Can you:

No. At run time, functions do not have names. They have addresses.

Described here: http://gammon.com.au/callbacks