Last active
December 15, 2015 19:49
-
-
Save theterg/5314097 to your computer and use it in GitHub Desktop.
SleepPulseFirmware.ino
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
/* | |
* Sleep Pulse Firmware - firmware code for the Sleep Pulse Device. | |
* This runs on an Atmel 328P microprocessor, at 5 volts, with a 16 MHz crystal | |
* Within the arduino IDE, select "Arduino Pro or Pro Mini (5V, 16MHz) w/ATmega328" as the Board | |
*/ | |
#include <math.h> | |
#include <EEPROM.h> | |
// Prototype pin definitions - don't change these! | |
#define LED1 3 | |
#define LED2 5 | |
#define LED3 6 | |
#define BUTPWR 8 | |
#define POWER 9 | |
#define MOTOR 2 | |
// Change-able settings: | |
// How long do we need to hold down the button to turn off the device? (in milliseconds) | |
#define LONG_PRESS 2000 | |
// When the button is pressed, how long do the LEDs stay turned on? | |
#define LED_WAKE_INTERVAL 5000 | |
// Lists of possible device settings | |
// You can change these to adjust the different options available to the user. | |
// intensities: different solenoid "strength" settings | |
// each value corresponds to the number of microseconds to turn on the solenoid | |
// really just need to experiment with different values. Anything beyond 5000 is pretty much the maximum. | |
#define NUM_INTENSITY_SETTINGS 3 | |
unsigned long intensities[NUM_INTENSITY_SETTINGS] = {1000, 2000, 5000}; | |
// intensities: different solenoid frequency settings | |
// each value corresponds to the number of milliseconds to wait before generating another heart beat. | |
// 1000 = 1Hz, 2000 = 0.5Hz, 500 = 2Hz | |
#define NUM_FREQUENCY_SETTINGS 3 | |
unsigned long frequencies[NUM_FREQUENCY_SETTINGS] = {2000, 1000, 500}; | |
// durations: how long to wait until the device turns itself off, in minutes | |
// currently only the middle value in this list is used as the user has no way to pick between them. | |
#define NUM_DURATION_SETTINGS 3 | |
unsigned long durations[NUM_DURATION_SETTINGS] = {10, 15, 20}; | |
// on start up, which item in the lists above to we use? | |
// These start at 0, so 1 is the middle item in the list | |
// any code in readSettingsFromEEPROM() below will override these default values. | |
char intensity_pos = 1; | |
char frequency_pos = 1; | |
char duration_pos = 1; | |
// These are other variables used in the code, nothing to change here. | |
char buff[200]; | |
unsigned long lastloop; | |
boolean last_butpwr; | |
boolean last_butsel; | |
unsigned long butpwr_time; | |
unsigned long butsel_time; | |
unsigned char selection = 0; | |
void setup() | |
{ | |
// Initialze the motor (solenoid), but make sure it's off | |
digitalWrite(MOTOR, LOW); | |
pinMode(MOTOR, OUTPUT); | |
// Flash all of the LEDs for 500 ms | |
analogWrite(LED1, 255); | |
analogWrite(LED2, 255); | |
analogWrite(LED3, 255); | |
delay(500); | |
analogWrite(LED1, 0); | |
analogWrite(LED2, 0); | |
analogWrite(LED3, 0); | |
// Turn on the POWER pin. | |
// If the user releases the power button before this point, the device will turn back off. | |
pinMode(POWER, OUTPUT); | |
digitalWrite(POWER, HIGH); | |
// There, now the SPD will remain powered on until POWER is set LOW again. | |
pinMode(BUTPWR, INPUT); | |
digitalWrite(BUTPWR, LOW); | |
// Recall any settings that we saved when the SPD was last turned off: | |
readSettingsFromEEPROM(); | |
// Set up the serial terminal, and send a welcome message. | |
Serial.begin(9600); | |
Serial.println("Instructions:"); | |
Serial.println("Enter I<value> to set Intensity. Value: 1-100 percent"); | |
Serial.println("Enter F<value> to set Frequency. Value: 10-5000 milliseconds"); | |
Serial.println("Enter D<value> to set Duration. Value: 1-120 minutes"); | |
Serial.println("Enter R<Parameter><Value> to recall a setting."); | |
Serial.println(" Where Parameter = 'I','F' or 'D' and Value = '0', '1' or '2'"); | |
// Set up the current state | |
lastloop = millis(); | |
last_butpwr = false; | |
} | |
// Loop() is run once for every time that the solenoid fires | |
void loop() | |
{ | |
// Stay in this loop while it's too early for the solenoid to fire | |
// lastloop is the last time loop() was run | |
// frequencies is a list of the different motor frequencies | |
// frequency_pos the current frequency setting | |
while (millis() < lastloop+frequencies[frequency_pos]){ | |
// Check if the user has pressed any buttons | |
processButtons(); | |
// Serial.available() will be > 0 if the user has typed something into the serial terminal | |
if (Serial.available() > 0){ | |
// wait until the user as entered an entire line (ending with <ENTER>) | |
readLine(); | |
processLine(); | |
} | |
} | |
// make a heart beat! The heart beat will feel stronger if the solenoid is turned on for longer | |
// intensities is a list containing the different motor strengths | |
// intensity_pos is the current intensity setting | |
// So turn on the motor, wait for a certian amount of time dictated by the intensity, and turn it back off | |
digitalWrite(MOTOR, HIGH); | |
delayMicroseconds(intensities[intensity_pos]); | |
digitalWrite(MOTOR, LOW); | |
// Print the current intensity and frequency to the serial terminal, helpful to see whats going on | |
Serial.print("Current intensity "); | |
Serial.print(intensities[intensity_pos]); | |
Serial.print(" frequency "); | |
Serial.println(frequencies[frequency_pos]); | |
// we must update lastloop otherwise we'll toggle the motor too quickly | |
lastloop = millis(); | |
} | |
// The EEPROM is the only *persistent* storage we have | |
// Anything that we want to be user-adjustable, but saved when the SPD is turned off must be stored in it. | |
void readSettingsFromEEPROM() { | |
// Currently only intensity is stored in the EEPROM | |
// TODO: store a larger 'STRUCT' in the EEPROM to store multiple settings. | |
intensity_pos = EEPROM.read(0); | |
} | |
void saveSettingsToEEPROM() { | |
// Only write to the EEPROM if the value has changed! | |
// It's important to do this - the EEPROM has a limited number of times it can be written to. | |
if (intensity_pos != EEPROM.read(0)){ | |
EEPROM.write(0, intensity_pos); | |
} | |
} | |
void allLEDsOff() { | |
analogWrite(LED1, 0); | |
analogWrite(LED2, 0); | |
analogWrite(LED3, 0); | |
} | |
// This must be called repeatedly to blink the LEDs in time with the heart beat. | |
void displayLevel() { | |
// brightness varies with the heart beat cycle: | |
// when the motor has just recently fired, millis() will be very close to lastloop | |
// this causes brightness to be roughly equal to 1000/4, or 250 (close to the maximum) | |
// as we get closer to when the motor is supposed to fire, millis()-lastloop will increase | |
unsigned char brightness = (frequencies[frequency_pos] - (millis()-lastloop))/4 ; | |
// Depending on what intensity level is selected, blink that LED. | |
if (intensity_pos == 0) { | |
analogWrite(LED1, brightness); | |
analogWrite(LED2, 0); | |
analogWrite(LED3, 0); | |
} else if (intensity_pos == 1) { | |
analogWrite(LED2, brightness); | |
analogWrite(LED1, 0); | |
analogWrite(LED3, 0); | |
} else if (intensity_pos == 2) { | |
analogWrite(LED3, brightness); | |
analogWrite(LED1, 0); | |
analogWrite(LED2, 0); | |
} | |
} | |
void processButtons() { | |
// Read the current value of the button (either 0 for not pressed, or 1 for pressed) | |
boolean butpwr = digitalRead(BUTPWR); | |
// Look for when the button value has CHANGED. This only runs once for each time the button is pressed OR released. | |
if (butpwr != last_butpwr) { | |
// If the button has changed, and it's now HIGH, button must have been just pressed. | |
if (butpwr) { | |
Serial.println("PWR PRESSED"); | |
// If the button had been pressed within the last LED_WAKE_INTERVAL, change the intensity | |
// This makes it so that you press the button once to turn on the LEDs, and as second time to change the setting | |
if (millis() < butpwr_time+LED_WAKE_INTERVAL) { | |
// '(intensity_pos+1)%NUM_INTENSITY_SETTINGS' increases the intensity_pos value, making sure it's in range | |
intensity_pos = (intensity_pos+1)%NUM_INTENSITY_SETTINGS; | |
} | |
butpwr_time = millis(); | |
} else { | |
// We don't do anything special when the button is released. | |
Serial.println("PWR released"); | |
} | |
last_butpwr = butpwr; | |
} | |
// If the button has been pressed within the last LED_WAKE_INTERVAL, flash the LEDs | |
if (millis() < butpwr_time+LED_WAKE_INTERVAL) { | |
displayLevel(); | |
} else { | |
allLEDsOff(); | |
} | |
// Check to see if it's time to turn the SPD off. There are two conditions here: | |
// 1) durations[duration_pos] is the turn off time, in minutes. Check if we have been turned on longer than that | |
// 2) check to see if the button has been pressed for more than LONG_PRESS seconds. If so, turn off | |
if ((millis() > durations[duration_pos]*60000)||(butpwr && (millis()-butpwr_time > LONG_PRESS))) { | |
TurnMyselfOff(); | |
} | |
delay(10); | |
} | |
// This will turn off the power switch. Nothing will run after this function is called. | |
void TurnMyselfOff() { | |
saveSettingsToEEPROM(); | |
// Flash the LEDs for 500 ms | |
analogWrite(LED1, 255); | |
analogWrite(LED2, 255); | |
analogWrite(LED3, 255); | |
delay(500); | |
allLEDsOff(); | |
Serial.println("Goodbye!"); | |
// Turn the power switch off. | |
// SPD will remain powered until the user releases the button | |
cli(); | |
digitalWrite(POWER, LOW); | |
sei(); | |
// Do absolutely nothing until the user releases the button | |
// Otherwise we will accidentally turn back on again. | |
// asm("nop"); makes sure that this loop will not be removed by the compiler. | |
while(1) { asm("nop"); } | |
} | |
// analyze a command that the user entered into the serial terminal. | |
// this is still a work in progress. | |
void processLine() { | |
Serial.print("Got: "); | |
Serial.println(buff); | |
long value = atol(buff+1); | |
if ((buff[0] == 'I')||(buff[0] == 'i')) { | |
//Intensity | |
intensities[intensity_pos] = value*100; | |
} else if ((buff[0] == 'F')||(buff[0] == 'f')) { | |
//Frequency | |
frequencies[frequency_pos] = value; | |
} else if ((buff[0] == 'T')||(buff[0] == 't')) { | |
//Time | |
duration = value; | |
} else if ((buff[0] == 'S')||(buff[0] == 's')) { | |
//Store | |
char setting = buff[1]; | |
char value = atoi(buff+2); | |
if ((setting == 'I')||(setting == 'i')) { | |
//intensities[value] = intensity; | |
} else if ((setting == 'F')||(setting == 'f')) { | |
//frequencies[value] = frequency; | |
} else if ((setting == 'T')||(setting == 't')) { | |
//durations[value] = duration; | |
} | |
} else if ((buff[0] == 'R')||(buff[0] == 'r')) { | |
//Recall | |
char setting = buff[1]; | |
char value = atoi(buff+2); | |
if ((setting == 'I')||(setting == 'i')) { | |
//intensity = intensities[value]; | |
} else if ((setting == 'F')||(setting == 'f')) { | |
//frequency = frequencies[value]; | |
} else if ((setting == 'T')||(setting == 't')) { | |
//duration = durations[value]; | |
} | |
} | |
else if ((buff[0] == 'L')||(buff[0] == 'l')) { | |
//LEDs | |
char led = atoi(buff+1); | |
char color = atoi(buff+2); | |
int value = atoi(buff+3); | |
char channel = 2+led+color; | |
if ((led == 2) && (color > 0)) { | |
channel += 3; | |
} | |
} | |
} | |
//Read a single line, terminated with a '\n' character | |
void readLine(){ | |
memset(buff, 0x0, 200); //Clear the previous contents of the buffer first | |
char c = ' '; | |
int idx = 0; | |
while (c != '\n'){ | |
c = Serial.read(); | |
if ((c > 31) && (c != '\r') && (c != '\n')) { //Filter out unwanted non-text characters. | |
buff[idx++] = c; //Add the character to the next position in the 'line' array | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment