Tutorial
Skeleton Sketch
Like most volunteers I try to offer programming solutions that are written at an introductory level.
As an example, we respond to new users use of delay( ) by recommending a delay based on the millis( ) or micros( ) functions.
When it comes to handling switches, we suggest looking at when an input changes state rather than the state of the input.
When it comes to sequential code, we offer the user to use a State Machine.
At some point in our programming, we leave behind basic techniques and advance to more powerful C++ methods.
As with many here, I have different Skeleton Templates that I use when starting out on a new project.
Attached below is one template version I use.
This version contains most of the normal sections often found in a sketch.
I start with deleting those sections that will not be needed then flesh out the remaining code to accomplish my task.
As a summary, this Skeleton Sketch has the following sections:
• a class that handles digital inputs
• a class that creates TIMER objects
• diagnostic code to help in debugging
• a basic State Machine
• a code block that handles I2C and parallel LCDs
• a list of often used macros
I offer this version of the skeleton as an example to new users so it might aid in their programming.
I should point out this is a work in progress and just as with all other software is always in flux; as an example, I recently added pause and release capability to the TIMER class which can pause a TIMER when you need more time allocation to handle a situation. You can then release the TIMER when normal code operation can return.
New users feel free to add or subtract to make this your own tool.
If others have suggestions or comments, feel free to mention them here.
In this example, all items are in one .ino file. You would of course not usually do this.
Note:
Normally we organize our code source in one of two ways:
• moving a class into its own library folder
• move class items and things like macros and other often used pieces of code into a separate folder where the .ino folder is located.
We might call this folder Header.h then access it with #include “Header.h” at the top of the .ino file.
//
//
// ___BasicSkeleton.ino
//
//
// Version YY/MM/DD Comments
// ======= ======== ====================================================================
// 1.00 23/02/13 Running code
//
//
//
//include macros class definitions and other items
//#include "Header.h"
//
//
// M a c r o s
//********************************************^************************************************
//
#define EXPIRED true
#define stillTIMING false
#define ENABLED true
#define DISABLED false
#define LEDon HIGH //+5V---[220R]---[LED]---PIN
#define LEDoff LOW
#define OPENED HIGH //+5V---[Internal 50k]---PIN---[Switch]---GND
#define CLOSED LOW
//#define OPENED LOW //+5V---[Switch]---PIN---[10k]---GND
//#define CLOSED HIGH
// F o r d i a g n o s t i c s
//********************************************^************************************************
// Atmega328: UNO, ProMini or Nano
//
// Note:
// PIND D0 to D7
// D0=0x01 D1=0x02 D2=0x04 D3=0x08 D4=0x10 D5=0x20 D6=0x40 D7=0x80
// PIND0 PIND1 PIND2 PIND3 PIND4 PIND5 PIND6 PIND7
//
// PINB D8 to D13
// D8=0x01 D9=0x02 D10=0x04 D11=0x08 D12=0x10 D13=0x20
// PINB0 PINB1 PINB2 PINB3 PINB4 PINB5
// PINC D14 to D19
// D14=0x01 D15=0x02 D16=0x04 D17=0x08 D18=0x10 D19=0x20
// PINC0 PINC1 PINC2 PINC3 PINC4 PINC5
//
//Macro definition for generating a 63ns pulse on UNO pin 13 i.e. PINB5
//
//add this line to setup()
//pinMode(13,OUTPUT);
//
//if used: PULSE62_13;
//
#define PULSE62_13 cli(); PINB = bit(PINB5); PINB = bit(PINB5); sei()
//********************************************^************************************************
// S e r i a l O R P a r a l l e l L C D ?
//********************************************^************************************************
//uncomment the next line if you have a serial LCD <-------<<<<<
#define serialLCDisBeingUsed
//****************************************
#ifdef serialLCDisBeingUsed
#include <Wire.h>
//Use I2C library: https://github.com/duinoWitchery/hd44780
//LCD Reference: https://www.arduino.cc/en/Reference/LiquidCrystal
#include <hd44780.h> //main hd44780 header
//NOTE:
//hd44780_I2Cexp control LCD using I2C I/O expander backpack (PCF8574 or MCP23008)
//hd44780_I2Clcd control LCD with native I2C interface (PCF2116, PCF2119x, etc...)
#include <hd44780ioClass/hd44780_I2Cexp.h> //I2C expander i/o class header
//If you do not know what your I2C address is, first run the "I2C_Scanner" sketch
//OR
//run the "I2CexpDiag" sketch that comes with the hd44780 library
//hd44780_I2Cexp lcd(0x3F);
hd44780_I2Cexp lcd(0x27);
//****************************************
#else
#include <LiquidCrystal.h>
// LCD pin 4 6 11 12 13 14
// RS EN D4 D5 D6 D7
LiquidCrystal lcd( 4, 5, 6, 7, 8, 9);
#endif
// c l a s s S w i t c h M a n a g e r V e r 2
//********************************************^************************************************
//Class for managing switch presses
//Author: Nick Gammon http://gammon.com.au/Arduino/SwitchManager.zip
//Date: 18 December 2013
//Modified: 12 February 2015 to pass pin number to function
//
//Modified by LarryD
//
/*
Example:
#include <SwitchManager.h>
SwitchManager mySwitch; // declare
//- newState will be LOW or HIGH (the is the state the switch is now in)
//- interval will be how many mS between the opposite state and this one
//- whichPin will be which pin caused this change
//- you can share the function amongst multiple Switches
void handleSwitchPress (const byte newState, const unsigned long interval, const byte whichPin)
{
}
void setup ()
{
mySwitch.begin (2, handleSwitchPress);
}
void loop ()
{
mySwitch.check (); // check for presses
}
*/
//when needed
//#include <Arduino.h>
//****************************************
class SwitchManagerVer2
{
enum { debounceTime = 5, noSwitch = -1 };
typedef void (*handlerFunction) (const byte newState,
const unsigned long interval,
const byte whichSwitch);
int pinNumber_;
handlerFunction f_;
byte oldSwitchState_;
unsigned long switchPressTime_; //when the switch last changed state
unsigned long lastLowTime_;
unsigned long lastHighTime_;
public:
//****************************************
//constructor
SwitchManagerVer2()
{
pinNumber_ = noSwitch;
f_ = NULL;
oldSwitchState_ = HIGH;
switchPressTime_ = 0;
lastLowTime_ = 0;
lastHighTime_ = 0;
}
//****************************************
void begin (const int pinNumber, handlerFunction f)
{
pinNumber_ = pinNumber;
f_ = f;
if (pinNumber_ != noSwitch)
pinMode (pinNumber_, INPUT_PULLUP);
} //end of begin
//****************************************
void check ()
{
//we need a valid pin number and a valid function to call
if (pinNumber_ == noSwitch || f_ == NULL)
{
return;
}
//*****************
//debounce
if (millis () - switchPressTime_ >= debounceTime)
{
//time when we closed the switch
switchPressTime_ = millis ();
//see if switch is open or closed
byte switchState = digitalRead (pinNumber_);
//*****************
//has it changed since last time ?
if (switchState != oldSwitchState_)
{
//remember for next time
oldSwitchState_ = switchState;
//*****************
if (switchState == LOW)
{
lastLowTime_ = switchPressTime_;
f_ (LOW, lastLowTime_ - lastHighTime_, pinNumber_);
}
//*****************
else
{
lastHighTime_ = switchPressTime_;
f_ (HIGH, lastHighTime_ - lastLowTime_, pinNumber_);
}
} //END of state change
} //END if debounce time up
} //END of check()
}; //END of class SwitchManager
// c l a s s M a k e T i m e r
//********************************************^************************************************
//a class to create TIMER objects
class MakeTimer
{
#define MILLIS true
#define MICROS false
#define ENABLED true
#define DISABLED false
#define YES true
#define NO false
//********************************************^************************************************
// TIMER example:
// give this TIMER a name "myTimer"
//
// MakeTimer myTimer =
// {
// 2000ul, //.interval (unsigned long)
// MILLIS, //.timerType (MILLIS or MICROS)
// NO, //.restart (YES or NO)
// DISABLED, //.enableFlag (ENABLED or DISABLED)
// YES ; //.suspendable (YES or NO)
// };
// This TIMER has:
// interval of 2 seconds, millis() based TIMER, it does not restart, it is disabled, and it can be suspended
//
// TIMERs can be paused or released if they have their ".suspendable" member set to YES (true).
// You can use this ability to "pause" a group of TIMERs when you need high speed processing and do not want other
// events from slowing code execution. When finised, you can "release" the group of paused TIMERs.
//
// N o t e: with delays of < 2 millis, use micros() "MICROS" and adjust "interval" as needed <-----<<<<
//
//You have access to:
//Variables: myTimer.interval, myTimer.timerType, myTimer.restart, myTimer.enableFlag, myTimer.suspendable
//
//Functions: myTimer.checkTimer(), myTimer.isEnabled(), myTimer.enableTimer(), myTimer.disableTimer(),
// myTimer.expireTimer(), MakeTime::pauseTimers(), MakeTime::releaseTimers(), myTimer.restartTimer()
private:
unsigned long previousTime = 0;
unsigned long currentTime = 0;
//static variables must be initialized after the class definition (i.e. bool MakeTimer::pauseFlag = false;)
static bool pauseFlag; //if true, TIMERs which are "suspendable" are paused
public:
//these 5 "members" are needed to define a TIMER
unsigned long interval; //delay time (ms/us) which we are looking for
bool timerType; //what kind of TIMER is this, MILLIS or MICROS
bool restart; //do we start this TIMER again and again, YES or NO
bool enableFlag; //tells us if this TIMER is ENABLED or DISABLED
bool suspendable; //can this TIMER be suspended ? YES or NO
//****************************************
//default constructor
MakeTimer()
{
//do nothing
}
//****************************************
//constructor with parameters
MakeTimer(unsigned long interval_, bool timerType_, bool restart_, bool enableFlag_, bool suspendFlag_)
{
interval = interval_;
timerType = timerType_;
restart = restart_;
enableFlag = enableFlag_;
suspendable = suspendFlag_;
}
//****************************************
//Function to check if this TIMER has expired ex: myTimer.checkTimer();
bool checkTimer()
{
//*********************
//is this a suspendable TIMER and is it paused ?
if (suspendable == YES && pauseFlag == true)
{
//this TIMER is paused
return false;
}
currentTime = getTime();
//*********************
//has this TIMER expired ?
if (currentTime - previousTime >= interval)
{
//*********************
//should this TIMER start again?
if (restart == ENABLED)
{
//get ready for the next iteration
previousTime = currentTime;
}
//this TIMER has expired
return true;
}
//this TIMER has not expired
return false;
} //END of checkTimer()
//****************************************
//Function to see if this TIMER is enabled, ex: myTimer.isEnabled();
bool isEnabled()
{
return enableFlag;
} //END of isEnabled()
//****************************************
//Function to "enable" and "restart" this TIMER, ex: myTimer.enableTimer();
void enableTimer()
{
enableFlag = ENABLED;
//initialize previousTime to current millis() or micros()
previousTime = getTime();
} //END of enableTimer()
//****************************************
//Function to disable this TIMER, ex: myTimer.disableTimer();
void disableTimer()
{
enableFlag = DISABLED;
} //END of disableTimer()
//****************************************
//Function to force this TIMER to expire ex: myTimer.expireTimer();
void expireTimer()
{
//force this TIMER to expire
previousTime = getTime() - interval;
} //END of expireTimer()
//****************************************
//Function to pause suspendable TIMERs ex: MakeTimer::pauseTimers();
static void pauseTimers()
{
pauseFlag = ENABLED;
} //END of pauseTimers()
//****************************************
//Function to release suspendable TIMERs ex: MakeTimer::releaseTimers();
static void releaseTimers()
{
pauseFlag = DISABLED;
} //END of releaseTimers()
//****************************************
//Function to restart this TIMER ex: myTimer.restartTimer();
void restartTimer()
{
//restart this TIMER
previousTime = getTime();
} //END of restartTimer()
//****************************************
//Function to return the current time
unsigned long getTime()
{
//return the time i.e. the value returned from millis() or micros()
//*********************
if (timerType == MILLIS)
{
return millis();
}
return micros();
} //END of getTime()
}; //END of class “MakeTimer”
//****************************************
//initialize static member pauseFlag of class MakeTimer
bool MakeTimer::pauseFlag = false;
// T I M E R o b j e c t s
//********************************************^************************************************
//make our "TIMER" objects
//these 5 "members" are needed to define a TIMER
//
//interval delay time (ms/us) which we are looking for
//timerType what kind of TIMER is this, MILLIS or MICROS
//restart do we start this TIMER again and again, YES or NO
//enableFlag tells us if this TIMER is ENABLED or DISABLED
//suspendable can this TIMER be suspended ? YES or NO
//****************************************
MakeTimer heartbeatTIMER =
{
500ul, //.interval (an unsigned long value)
MILLIS, //.timerType (MILLIS or MICROS)
YES, //.restart (YES or NO)
ENABLED, //.enabledFlag (ENABLED or DISABLED)
YES //.suspendable (YES or NO)
};
//****************************************
MakeTimer stateMachineTIMER =
{
1000ul, //.interval (an unsigned long value)
MICROS, //.timerType (MILLIS or MICROS)
YES, //.restart (YES or NO)
ENABLED, //.enabledFlag (ENABLED or DISABLED)
NO //.suspendable (YES or NO)
};
//****************************************
MakeTimer commonTIMER =
{
200ul, //.interval (an unsigned long value)
MILLIS, //.timerType (MILLIS or MICROS)
YES, //.restart (YES or NO)
DISABLED, //.enabledFlag (ENABLED or DISABLED)
NO //.suspendable (YES or NO)
};
// S t a t e M a c h i n e S t u f f
//********************************************^************************************************
//define the "states" in our State Machine
enum SM {Startup, State1, State2, State3};
SM machineState = Startup;
// s w i t c h i n s t a n t i a t i o n s
//********************************************^************************************************
//make our "Switch" objects
SwitchManagerVer2 toggleLEDswitch;
// G P I O & V a r i a b l e s
//********************************************^************************************************
//
const byte toggleLEDswitchPin = 2; //+5V---[Internal 50k]---PIN---[Switch]---GND
const byte testLED = 3; //+5V---[220R]---[LED]---PIN
//pins used for a parallel LCD 4, 5, 6, 7, 8, 9
const byte machineTestLED = 12;
const byte heartbeatLED = 13;
//****************************************
byte counter;
// s e t u p ( )
//********************************************^************************************************
//
void setup()
{
Serial.begin(115200);
pinMode(testLED, OUTPUT);
pinMode(machineTestLED, OUTPUT);
pinMode(heartbeatLED, OUTPUT);
//used for diagnostics <-------<<<<<
//pinMode(13, OUTPUT); //in this case pin 13 is the heartbeat LED
//****************************************
//LCD stuff
lcd.begin(16, 2);
lcd.clear();
lcd.setCursor(0, 0);
// 111111
// 0123456789012345
// Skeleton Sketch
lcd.print("Skeleton Sketch");
//****************************************
//initialize "Switch" object(s)
toggleLEDswitch.begin (toggleLEDswitchPin, handleSwitches);
//****************************************
//initialize/restart TIMERs that are ENABLED at power up time
heartbeatTIMER.restartTimer();
stateMachineTIMER.restartTimer();
} //END of setup()
// l o o p ( )
//********************************************^************************************************
//
void loop()
{
//**************************************** D i a g n o s t i c s
//for diagnostics, print the loop() execution time
//
//for oscilloscope measuring
//two pulses indicate the beginning of loop
//PULSE62_13;
//PULSE62_13;
//
//print loop() execution time
//static unsigned long speedTime;
//Serial.println(micros() - speedTime);
//speedTime = micros();
//for oscilloscope measuring, the time when loop's main code actually starts
//PULSE62_13;
//**************************************** h e a r t b e a t T I M E R
//is it time to toggle the heartbeatLED ?
if (heartbeatTIMER.checkTimer() == EXPIRED)
{
//toggle the heartbeatLED
digitalWrite(heartbeatLED, !digitalRead(heartbeatLED));
}
//**************************************** s t a t e M a c h i n e T I M E R
//is it time to check the the State Machine ?
if (stateMachineTIMER.checkTimer() == EXPIRED)
{
checkMachine();
}
//========================================
//Other non blocking code goes here
//========================================
//**************************************** c h e c k s w i t c h e s
//check "Switches" at non blocking loop() speed !
//when a "Switch" changes state, function handleSwitches() is called
//
toggleLEDswitch.check();
} //END of loop()
// h a n d l e S w i t c h e s ( )
//********************************************^************************************************
//You get here "ONLY" if there has been a valid change in a "Switch's" state (>15ms).
//When a "Switch" has changed state, SwitchManagerWithFilter passes this function 3 arguments:
//"newState" - this will be HIGH or LOW. This is the state the "Switch" is in now.
//"interval" - the number of milliseconds the "Switch" stayed in the previous state
//"whichPin" - this is the "Switch" input pin that we are examining
void handleSwitches (const byte newState, const unsigned long interval, const byte whichPin)
{
switch (whichPin)
{
//**************************************** t o g g l e L E D s w i t c h P i n
case toggleLEDswitchPin:
{
//********************* CLOSED
//did the "Switch" closed ?
if (newState == CLOSED)
{
//toggle the testLED
digitalWrite(testLED, !digitalRead(testLED));
Serial.print("Switch was OPEN for \t");
Serial.print(interval);
Serial.println(" ms ");
}
//********************* OPENED
//the "Switch" OPENED
else
{
Serial.print("Switch was CLOSED for \t");
Serial.print(interval);
Serial.println(" ms \n");
}
}
break; //END of case:
//****************************************
//========================================
//other "Switches" go here
//========================================
} //END of switch/case
} //END of handleSwitches()
// c h e c k M a c h i n e ( )
//********************************************^************************************************
//
void checkMachine()
{
//****************************************
//service the "current" Machine State
switch (machineState)
{
//*********************
case Startup:
{
//do startup stuff
lcd.setCursor(0, 1);
// 111111
// 0123456789012345
// Counter = xx
lcd.print(" Counter = ");
//set the commonTIMER interval to 200ms
commonTIMER.interval = 200ul;
commonTIMER.enableTimer();
//next state
machineState = State1;
}
break; //END of case:
//************************
case State1:
{
//if enabled, is the commonTIMER expired ?
if (commonTIMER.isEnabled() && commonTIMER.checkTimer() == EXPIRED)
{
//toggle the machineTestLED
digitalWrite(machineTestLED, !digitalRead(machineTestLED));
if (counter++ > 49)
{
counter = 0;
commonTIMER.interval = 500;
lcd.setCursor(0, 1);
// 111111
// 0123456789012345
// Counter = xx
lcd.print(" Counter = ");
machineState = State2;
}
lcd.setCursor(12, 1);
// 111111
// 0123456789012345
// Counter = xx
lcd.print(counter);
//release all suspendable TIMERs
MakeTimer::releaseTimers();
}
//none commonTIMER code
}
break; //END of case:
//************************
case State2:
{
//if enabled, is the commonTIMER expired ?
if (commonTIMER.isEnabled() && commonTIMER.checkTimer() == EXPIRED)
{
//toggle the machineTestLED
digitalWrite(machineTestLED, !digitalRead(machineTestLED));
if (counter++ > 49)
{
counter = 0;
commonTIMER.interval = 200;
lcd.setCursor(0, 1);
// 111111
// 0123456789012345
// Counter = xx
lcd.print(" Counter = ");
machineState = State1;
}
lcd.setCursor(12, 1);
// 111111
// 0123456789012345
// Counter = xx
lcd.print(counter);
//pause all suspendable TIMERs
MakeTimer::pauseTimers();
}
//none commonTIMER code
}
break; //END of case:
//************************
case State3:
{
//do something
}
break; //END of case:
} //END of switch/case
} //END of checkMachine()
//********************************************^************************************************
// E N D O f S k e t c h
//********************************************^************************************************