Last active
August 29, 2015 14:03
-
-
Save houseofjeff/33b6ff6caf20293740dc to your computer and use it in GitHub Desktop.
Arduino Scheduler Framework w/ Remote-Control Sleep-mode
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//------------------------------------------------------------------------------ | |
// | |
// 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