Skip to content

Instantly share code, notes, and snippets.

@houseofjeff
Last active August 29, 2015 14:03
Show Gist options
  • Save houseofjeff/33b6ff6caf20293740dc to your computer and use it in GitHub Desktop.
Save houseofjeff/33b6ff6caf20293740dc to your computer and use it in GitHub Desktop.
Arduino Scheduler Framework w/ Remote-Control Sleep-mode
//------------------------------------------------------------------------------
//
// An Arduino Scheduler Framework with Remote-control Sleep Mode.
//
// For details, see my blog post:
// http://houseofjeff.com/2014/07/15/a-scheduling-framework-remote-control-sleep-mode-for-arduino/
//
// This sample is licensed under the MIT License (MIT)
// Copyright (c) 2014, by Jeff House
// Full text of license at the bottom of the file
//
// The pins we'll be using.
#define IR_PIN 2
#define LED_PIN 13
#define DIST_TRIGGER_PIN 9
#define DIST_ECHO_PIN 4
#define MOTOR_B1_PIN 3
#define MOTOR_B2_PIN 5
#define MOTOR_A2_PIN 6
#define MOTOR_A1_PIN 11
// Some limits for the reception of the IR message
#define MAX_MSG_SIZE 24 // We're only looking for 16 in any case.
#define MAX_PULSE 30000 // The maximum pulse we'll listen for
//------------------------------------------------------------------------------
// Global variables changed by the various task handlers
boolean g_isAsleep = true; // True if we're in sleep mode
uint8_t g_sleepLedState = LOW; // The current state of the Sleep LED
uint16_t g_distance = 0; // Distance to the nearest obstacle in cm
boolean g_ismoving = false; // True if the motors are engaged
//------------------------------------------------------------------------------
// Global variables changed by the IR message interrupt handler
volatile uint8_t g_irMsgLen = 0; // > 0 if there's a message waiting
volatile uint16_t g_irMsg[MAX_MSG_SIZE*2]; // Series of HIGH/LOW pulse lengths
//==============================================================================
// HANDLE THE RECOGNITION OF THE IR COMMAND
//==============================================================================
uint32_t then = 0; // the last time we saw a pulse
void captureIR() {
// If the previous call came in less than 0.25 seconds ago, just ignore it.
uint32_t now = micros();
if (now - then < 250000)
return;
then = now;
// Track the pin as it rises and falls and record the time it spends in HIGH
// then LOW state. If we pass MAX_PULSE uS, the message is over.
uint32_t hightime, lowtime;
g_irMsgLen = 0;
while (g_irMsgLen < MAX_MSG_SIZE*2) {
hightime = pulseIn(IR_PIN, HIGH, MAX_PULSE);
lowtime = pulseIn(IR_PIN, LOW, MAX_PULSE);
if ( (hightime == 0) || (lowtime == 0) ) {
// Finished with message
return;
}
g_irMsg[g_irMsgLen++] = hightime/28;
g_irMsg[g_irMsgLen++] = lowtime/28;
}
}
uint16_t powerIrSeq[16*2] = { 15,15, 15,15, 15,15, 15,15, 15,15, 15,15, 15,15, 46,15, 15,15, 15,15, 46,15, 46,15, 46,15, 46,15, 46,15, 15,15 };
boolean isMatch() {
uint32_t sumOfSquares = 0;
// start at two because the first pair can vary wildly based on initial
// timing, but it's possible I should be starting with 1.
for (uint8_t i = 2; i < g_irMsgLen; i++) {
int16_t delta = g_irMsg[i] - powerIrSeq[i];
sumOfSquares += (delta*delta);
}
// the variance is the sum of the squares of the error, divided by the
// size of the input.
uint16_t variance = sumOfSquares/( sizeof(powerIrSeq)/sizeof(powerIrSeq[0]) );
// Call it a match if the average difference per reading is 3uS or less (squared).
return (variance < 3*3);
}
//==============================================================================
// MANAGE TASK LISTS
//==============================================================================
// A task function accepts no input and returns no result
typedef void (*TASKFUNC)();
// The task list is a linked list of individual tasks, which include...
typedef struct _task {
TASKFUNC taskfunc; // ...the function to call on each loop
TASKFUNC onSleep; // ...the function to call on sleeping (if any)
TASKFUNC onWake; // ...the function to call on waking (if any)
uint16_t everyMs; // ...how often to call it
uint32_t nextMs; // ...the last time it was called
struct _task* nextTask; // ...a pointer to the next task (or NULL)
} TASK;
// We define two task lists, one each for the Sleeping & Awake states.
// (This could obviously be extended to having a different task list for
// any arbitrary set of states you might want to define for your project)
TASK* _pAwakeTaskHead = NULL;
TASK* _pSleepTaskHead = NULL;
//------------------------------------------------------------------------------
// addTask() adds a new Task to the end of the supplied linked list. If pHead
// is NULL, it will change to hold the first node in the list.
int addTask( TASK*& pHead, void(*taskfunc)(), void(*onwake)(), void(*onsleep)(), uint16_t periodMs) {
// Create the new task
TASK* pTask = (TASK*) malloc( sizeof(TASK) );
pTask->everyMs = periodMs;
pTask->nextMs = 0;
pTask->taskfunc = taskfunc;
pTask->onSleep = onsleep;
pTask->onWake = onwake;
pTask->nextTask = NULL;
// If this is the first task, make it the head
if (pHead == NULL) {
pHead = pTask;
}
else {
// Add this to the tail. It would be faster to store the tail of the list in
// memory, but this list is small and we only need it here.
TASK* tail = pHead;
while (tail->nextTask != NULL)
tail = tail->nextTask;
tail->nextTask = pTask;
}
}
//------------------------------------------------------------------------------
// runTaskList() executes a linked list of tasks, checking the last executed
// time for each entry, and executing the taskfunc for it if more than everyMs
// milliseconds has passed.
//
// After each task, check to see if an IR message has come in, and if it
// matches, toggle the sleep state, execute any onWake() or onSleep() functions
// as appropriate, and then return.
void runTaskList( struct _task* head ) {
TASK* current = head;
// Iterate through the tasks in the task list
while (current != NULL) {
// If it's time, execute the next task in the list
uint32_t currentMs = millis();
if (currentMs >= current->nextMs) {
(current->taskfunc)();
current->nextMs = currentMs + current->everyMs;
}
current = current->nextTask;
// If during that process, we received an IR message...
if (g_irMsgLen > 0) {
// ...test to see if it matches our pattern...
if (isMatch()) {
// ...and if it does, toggle the sleep state
g_isAsleep = !g_isAsleep;
g_irMsgLen = 0;
if (g_isAsleep)
Serial.println("Sleeping");
else
Serial.println("Waking up");
// ...excute the onSleep() or onWake() functions as respectively...
TASK* current = head;
while (current != NULL) {
if (g_isAsleep && (current->onSleep != NULL))
(current->onSleep)();
else if (!g_isAsleep && (current->onWake != NULL))
(current->onWake)();
current=current->nextTask;
}
// ...and break out of the current loop so we can go back and get
// the new task list
return;
}
}
}
}
//==============================================================================
// TASK FUNCTIONS
//==============================================================================
// this is called while the system is in sleep mode
void checkSleep() {
g_sleepLedState = (g_sleepLedState == LOW) ? HIGH : LOW;
digitalWrite( LED_PIN, g_sleepLedState );
}
void clearLed() {
// when we exit sleep mode, make sure to turn off the LED
digitalWrite( LED_PIN, LOW );
}
// Get the current distance value from the ultrasound sensor and store
// it in a global variable for others to use and enjoy.
void testDistance() {
digitalWrite(DIST_TRIGGER_PIN, LOW);
delayMicroseconds(2);
digitalWrite(DIST_TRIGGER_PIN, HIGH);
delayMicroseconds(10);
digitalWrite(DIST_TRIGGER_PIN, LOW);
uint16_t duration = pulseIn(DIST_ECHO_PIN, HIGH);
uint16_t distance = duration/29/2;
if (distance < 300) {
// Sometimes it throws values that are way out of bounds, just ignore those
g_distance = distance;
}
}
// Keep the motors running as long as the distance sensor reports less than 20cm
// from an obstacle.
void driveStraight() {
if (g_distance < 20) {
allStop();
}
else {
setSpeeds( 200, 0, 200, 0 );
}
}
void allStop() {
// When the robot goes to sleep, turn off the engines
setSpeeds(0,0,0,0);
}
void setSpeeds( int b1, int b2, int a2, int a1 )
{
analogWrite(MOTOR_B1_PIN, b1);
analogWrite(MOTOR_B2_PIN, b2);
analogWrite(MOTOR_A2_PIN, a2);
analogWrite(MOTOR_A1_PIN, a1);
}
//==============================================================================
// SETUP() and LOOP()
//==============================================================================
void setup() {
Serial.begin(9600);
// Set the pin modes
pinMode( IR_PIN, INPUT ); // IR receiver signal line
pinMode( LED_PIN, OUTPUT ); // pin 13 for LED flashing
pinMode(DIST_TRIGGER_PIN, OUTPUT); // Distance sensor trigger
pinMode(DIST_ECHO_PIN, INPUT); // Distance sensor echo
pinMode(MOTOR_A1_PIN, OUTPUT); // Left motor forward
pinMode(MOTOR_A2_PIN, OUTPUT); // Left motor backward
pinMode(MOTOR_B1_PIN, OUTPUT); // Right motor forward
pinMode(MOTOR_B2_PIN, OUTPUT); // Riht motor backward
// When we're in sleep mode, we won't do much except blink an LED
addTask( _pSleepTaskHead, &checkSleep, &clearLed, NULL, 500 );
// When we're awake, drive forward until we're close to something
addTask( _pAwakeTaskHead, &driveStraight, NULL, &allStop, 250 );
addTask( _pAwakeTaskHead, &testDistance, NULL, NULL, 100 );
// And then attach the interrupt to watch for an IR message to sleep
attachInterrupt( 0, captureIR, RISING );
}
// Each time through the loop we're going to execute a task list. Which one
// gets run depends on whether or not we're in sleep mode.
void loop() {
// Each time through the loop, decide which task list to execute based on sleep state.
TASK* currentList = g_isAsleep ? _pSleepTaskHead : _pAwakeTaskHead;
// and then run it.
runTaskList(currentList);
}
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//------------------------------------------------------------------------------
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment