Closed 772pilot closed 8 years ago
Please post the code you are trying to compile or if you're using one of the examples unmodified then state which one.
Beware, there's quite a bit of code. Everything relevant is within the first 200 lines, however.
/*
* LightSaberOS V1.0
*
* released on: 10 mar 2016
* author: Sebastien CAPOU (neskweek@gmail.com)
* Source : https://github.com/neskweek/LightSaberOS
* Description: Operating System for Arduino based LightSaber
*
* This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
* To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/.
*/
#include <PalatisSoftPWM.h>
#include <Arduino.h>
#include <DFPlayer.h>
#include <I2Cdev.h>
#include <MPU6050_6Axis_MotionApps20.h>
#include <EEPROMex.h>
#include <OneButton.h>
#include <LinkedList.h>
#include <avr/sleep.h>
#include <avr/power.h>
#include "Buttons.h"
#include "Config.h"
#include "ConfigMenu.h"
#include "Light.h"
#include "SoundFont.h"
#if I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE
#include <Wire.h>
#endif
/*
* DO NOT MODIFY
* Unless you know what you're doing
*************************************/
#define CONFIG_VERSION "L01"
#define MEMORYBASE 32
#define SWING_SUPPRESS 700
#ifdef WRIST_MOVEMENTS
#define WRIST_SENSIBILITY 1000
#endif
/************************************/
/*
* BATTERY SAFETY MONITOR
* Modify to match your baterries specs
* rule of thumbs for serial wired batteries:
* LOW_BATTERY = 3200 * Nb_of_battery_cells
* CRITICAL_BATTERY = 3050 * Nb_of_battery_cells
*************************************/
#define LOW_BATTERY 3200
#define CRITICAL_BATTERY 3050
/************************************/
/*
* DEFAULT CONFIG PARAMETERS
* Will be overriden by EEPROM settings
* once the first save will be done
*************************************/
#define VOL 13
#define SOUNDFONT 2
#define SWING 800
/************************************/
/***************************************************************************************************
* Motion detection Variables
*/
MPU6050 mpu;
// MPU control/status vars
volatile bool mpuInterrupt = false; // indicates whether MPU interrupt pin has gone high
bool dmpReady = false; // set true if DMP init was successful
uint8_t mpuIntStatus; // holds actual interrupt status byte from MPU
uint8_t devStatus; // return status after each device operation (0 = success, !0 = error)
uint16_t packetSize; // expected DMP packet size (default is 42 bytes)
uint8_t fifoBuffer[64]; // FIFO storage buffer
uint16_t mpuFifoCount; // count of all bytes currently in FIFO
// orientation/motion vars
Quaternion quaternion; // [w, x, y, z] quaternion container
VectorInt16 aaWorld; // [x, y, z] world-frame accel sensor measurements
static Quaternion quaternion_last; // [w, x, y, z] quaternion container
static Quaternion quaternion_reading; // [w, x, y, z] quaternion container
static VectorInt16 aaWorld_last; // [x, y, z] world-frame accel sensor measurements
static VectorInt16 aaWorld_reading; // [x, y, z] world-frame accel sensor measurements
/***************************************************************************************************
* LED String variables
*/
#ifdef LEDSTRINGS
uint8_t ledPins[] = { LEDSTRING1, LEDSTRING2, LEDSTRING3, LEDSTRING4,
LEDSTRING5, LEDSTRING6 };
uint8_t blasterPin;
#endif
#ifdef LUXEON
uint8_t ledPins[] = {LED_RED, LED_GREEN, LED_BLUE};
uint8_t currentColor[4]; //0:Red 1:Green 2:Blue 3:ColorID
#endif
# ifdef ACCENT_LED
unsigned long lastAccent = millis();
#ifdef SOFT_ACCENT
unsigned long lastAccentTick = micros();
struct softPWM {
uint8_t dutyCycle; // in percent
bool revertCycle;
uint8_t state;
uint16_t tick;
} pwmPin = { 100, false, LOW, 0 };
#endif
#endif
uint8_t blaster = 0;
bool blasterBlocks = false;
uint8_t clash = 0;
bool lockup = false;
uint8_t blink = 0;
uint8_t randomBlink = 0;
SOFTPWM_DEFINE_PINA2_CHANNEL(0);
SOFTPWM_DEFINE_PINA3_CHANNEL(1);
/***************************************************************************************************
* Buttons variables
*/
OneButton mainButton(MAIN_BUTTON, true);
OneButton lockupButton(LOCKUP_BUTTON, true);
bool actionMode = false; // Play with your saber
bool configMode = false; // Configure your saber
static bool ignition = false;
static bool browsing = false;
/***************************************************************************************************
* DFPLAYER variables
*/
DFPlayer dfplayer;
SoundFont soundFont;
unsigned long sndSuppress = millis();
/***************************************************************************************************
* ConfigMode Variables
*/
int8_t modification = 0;
int16_t value = 0;
uint8_t menu = 0;
bool enterMenu = false;
bool changeMenu = false;
bool play = false;
unsigned int configAdress = 0;
long lastBatterycheck = 0;
bool lowBattery = false;
#ifdef DEEP_SLEEP
static long timeToDeepSleep = millis();
#endif
volatile uint8_t portbhistory = 0xFF; // default is high because the pull-up
#ifdef LEDSTRINGS
struct StoreStruct {
// This is for mere detection if they are our settings
char version[5];
// The settings
uint8_t volume; // 0 to 30
uint8_t soundFont; // as many Sound font you have defined in Soundfont.h Max:253
uint16_t swingTreshold; // treshold acceleration for Swing
uint8_t soundStringPreset[SOUNDFONT_QUANTITY + 2][3];
} storage;
#endif
#ifdef LUXEON
struct StoreStruct {
// This is for mere detection if they are our settings
char version[5];
// The settings
uint8_t volume;// 0 to 30
uint8_t soundFont;// as many as Sound font you have defined in Soundfont.h Max:253
uint16_t swingTreshold;// treshold acceleration for Swing
uint8_t mainColor;//colorID
uint8_t clashColor;//colorID
uint8_t soundFontColorPreset[SOUNDFONT_QUANTITY + 2][2];//colorIDs
// soundFontColorPreset[sndft][0] : main colorID
// soundFontColorPreset[sndft][1] : clash colorID
}storage;
#endif
// ====================================================================================
// === SETUP ROUTINE ===
// ====================================================================================
void setup() {
CSoftPWM.begin(60);
// join I2C bus (I2Cdev library doesn't do this automatically)
#if I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE
Wire.begin();
TWBR = 24; // 400kHz I2C clock (200kHz if CPU is 8MHz). Comment this line if having compilation difficulties with TWBR.
#elif I2CDEV_IMPLEMENTATION == I2CDEV_BUILTIN_FASTWIRE
Fastwire::setup(400, true);
#endif
#ifdef LS_INFO
// Serial line for debug
Serial.begin(115200);
#endif
#ifdef LS_DEBUG
// Serial line for debug
Serial.begin(115200);
#endif
/***** LOAD CONFIG *****/
// Get config from EEPROM if there is one
// or initialise value with default ones set in StoreStruct
EEPROM.setMemPool(MEMORYBASE, EEPROMSizeATmega328); //Set memorypool base to 32, assume Arduino Uno board
configAdress = EEPROM.getAddress(sizeof(StoreStruct)); // Size of config object
if (!loadConfig()) {
for (uint8_t i = 0; i <= 3; i++)
storage.version[i] = CONFIG_VERSION[i];
storage.soundFont = SOUNDFONT;
storage.volume = VOL;
storage.swingTreshold = SWING;
#ifdef LEDSTRINGS
storage.soundStringPreset[2][0] = 0; //PowerOn
storage.soundStringPreset[2][1] = 0; //PowerOff
storage.soundStringPreset[2][2] = 0; //Flickering
storage.soundStringPreset[3][0] = 1;
storage.soundStringPreset[3][1] = 1;
storage.soundStringPreset[3][2] = 0;
#endif
#ifdef LUXEON
storage.mainColor = 4;
storage.clashColor = 5;
storage.soundFontColorPreset[2][0] = 2;
storage.soundFontColorPreset[2][1] = 0;
storage.soundFontColorPreset[3][0] = 0;
storage.soundFontColorPreset[3][1] = 5;
#endif
#ifdef LS_INFO
Serial.println(F("DEFAULT VALUE"));
#endif
}
#ifdef LS_INFO
else {
Serial.println(F("EEPROM LOADED"));
}
#endif
#ifdef LUXEON
//initialise start color
getColor(currentColor, storage.mainColor);
#endif
/***** LOAD CONFIG *****/
/***** MP6050 MOTION DETECTOR INITIALISATION *****/
// initialize device
#ifdef LS_INFO
Serial.println(F("Initializing I2C devices..."));
#endif
mpu.initialize();
// verify connection
#ifdef LS_INFO
Serial.println(F("Testing device connections..."));
Serial.println(
mpu.testConnection() ?
F("MPU6050 connection successful") :
F("MPU6050 connection failed"));
// load and configure the DMP
Serial.println(F("Initializing DMP..."));
#endif
devStatus = mpu.dmpInitialize();
/*
* Those offsets are specific to each MPU6050 device.
* they are found via calibration process.
* See this script http://www.i2cdevlib.com/forums/index.php?app=core&module=attach§ion=attach&attach_id=27
*/
mpu.setXAccelOffset(-440);
mpu.setYAccelOffset(-699);
mpu.setZAccelOffset(974);
mpu.setXGyroOffset(60);
mpu.setYGyroOffset(-36);
mpu.setZGyroOffset(-63);
// make sure it worked (returns 0 if so)
if (devStatus == 0) {
// turn on the DMP, now that it's ready
#ifdef LS_INFO
Serial.println(F("Enabling DMP..."));
#endif
mpu.setDMPEnabled(true);
// enable Arduino interrupt detection
#ifdef LS_INFO
Serial.println(
F(
"Enabling interrupt detection (Arduino external interrupt 0)..."));
#endif
// attachInterrupt(0, dmpDataReady, RISING);
mpuIntStatus = mpu.getIntStatus();
// set our DMP Ready flag so the main loop() function knows it's okay to use it
#ifdef LS_INFO
Serial.println(F("DMP ready! Waiting for first interrupt..."));
#endif
dmpReady = true;
// get expected DMP packet size for later comparison
packetSize = mpu.dmpGetFIFOPacketSize();
} else {
// ERROR!
// 1 = initial memory load failed
// 2 = DMP configuration updates failed
// (if it's going to break, usually the code will be 1)
#ifdef LS_INFO
Serial.print(F("DMP Initialization failed (code "));
Serial.print(devStatus);
Serial.println(F(")"));
#endif
}
// configure the motion interrupt for clash recognition
// INT_PIN_CFG register
// in the working code of MPU6050_DMP all bits of the INT_PIN_CFG are false (0)
mpu.setInterruptMode(false); // INT_PIN_CFG register INT_LEVEL (0-active high, 1-active low)
mpu.setInterruptDrive(false); // INT_PIN_CFG register INT_OPEN (0-push/pull, 1-open drain)
mpu.setInterruptLatch(false); // INT_PIN_CFG register LATCH_INT_EN (0 - emits 50us pulse upon trigger, 1-pin is held until int is cleared)
mpu.setInterruptLatchClear(false); // INT_PIN_CFG register INT_RD_CLEAR (0-clear int only on reading int status reg, 1-any read clears int)
mpu.setFSyncInterruptLevel(false);
mpu.setFSyncInterruptEnabled(false);
mpu.setI2CBypassEnabled(false);
// Enable/disable interrupt sources - enable only motion interrupt
mpu.setIntFreefallEnabled(false);
mpu.setIntMotionEnabled(true);
mpu.setIntZeroMotionEnabled(false);
mpu.setIntFIFOBufferOverflowEnabled(false);
mpu.setIntI2CMasterEnabled(false);
mpu.setIntDataReadyEnabled(false);
mpu.setIntMotionEnabled(true); // INT_ENABLE register enable interrupt source motion detection
mpu.setMotionDetectionThreshold(10); // 1mg/LSB
mpu.setMotionDetectionDuration(2); // number of consecutive samples above threshold to trigger int
mpuIntStatus = mpu.getIntStatus();
#ifdef LS_CLASH_DEBUG
Serial.println("MPU6050 register setup:");
Serial.print("INT_PIN_CFG\t");
Serial.print(mpu.getInterruptMode());
Serial.print("\t");
Serial.print(mpu.getInterruptDrive());
Serial.print("\t");
Serial.print(mpu.getInterruptLatch());
Serial.print("\t");
Serial.print(mpu.getInterruptLatchClear());
Serial.print("\t");
Serial.print(mpu.getFSyncInterruptLevel());
Serial.print("\t");
Serial.print(mpu.getFSyncInterruptEnabled());
Serial.print("\t");
Serial.println(mpu.getI2CBypassEnabled());
// list INT_ENABLE register contents
Serial.print("INT_ENABLE\t");
Serial.print(mpu.getIntFreefallEnabled());
Serial.print("\t");
Serial.print(mpu.getIntMotionEnabled());
Serial.print("\t");
Serial.print(mpu.getIntZeroMotionEnabled());
Serial.print("\t");
Serial.print(mpu.getIntFIFOBufferOverflowEnabled());
Serial.print("\t");
Serial.print(mpu.getIntI2CMasterEnabled());
Serial.print("\t");
Serial.println(mpu.getIntDataReadyEnabled());
#endif
/***** MP6050 MOTION DETECTOR INITIALISATION *****/
/***** LED SEGMENT INITIALISATION *****/
// initialize ledstrings segments
DDRD |= B01101000;
DDRB |= B00001110;
//We shut off all pins that could wearing leds,just to be sure
PORTD &= B10010111;
PORTB &= B11110001;
#ifdef FoCSTRING
pinMode(FoCSTRING, OUTPUT);
FoCOff(FoCSTRING);
#endif
#ifdef ACCENT_LED
pinMode(ACCENT_LED, OUTPUT);
#endif
//Randomize randomness (no really that's what it does)
randomSeed(analogRead(2));
/***** LED SEGMENT INITIALISATION *****/
/***** BUTTONS INITIALISATION *****/
#ifdef DEEP_SLEEP
PCMSK0 = (1 << PCINT4); // set PCINT4 (PIN 12) to trigger an interrupt on state change
PCMSK2 = (1 << PCINT20); // set PCINT20 (PIN 4) to trigger an interrupt on state change
#endif
// link the Main button functions.
mainButton.setClickTicks(CLICK);
mainButton.setPressTicks(PRESS_CONFIG);
mainButton.attachClick(mainClick);
//mainButton.attachDoubleClick(mainDoubleClick);
mainButton.attachLongPressStart(mainLongPressStart);
mainButton.attachLongPressStop(mainLongPressStop);
mainButton.attachDuringLongPress(mainLongPress);
// link the Lockup button functions.
lockupButton.setClickTicks(CLICK);
lockupButton.setPressTicks(PRESS_CONFIG);
lockupButton.attachClick(lockupClick);
//lockupButton.attachDoubleClick(lockupDoubleClick);
lockupButton.attachLongPressStart(lockupLongPressStart);
lockupButton.attachLongPressStop(lockupLongPressStop);
lockupButton.attachDuringLongPress(lockupLongPress);
/***** BUTTONS INITIALISATION *****/
/***** DF PLAYER INITIALISATION *****/
dfplayer.setSerial(DFPLAYER_TX, DFPLAYER_RX);
dfplayer.setVolume(storage.volume);
delay(200);
pinMode(SPK1, INPUT);
pinMode(SPK2, INPUT);
soundFont.setID(storage.soundFont);
/***** DF PLAYER INITIALISATION *****/
//setup finished. Boot ready. We notify !
dfplayer.playPhysicalTrack(16);
delay(20);
}
// ====================================================================================
// === LOOP ROUTINE ===
// ====================================================================================
void loop() {
//Check battery level
if (lastBatterycheck == 0 or millis() - lastBatterycheck >= 60000) {
uint16_t voltage = readVcc();
if (voltage <= CRITICAL_BATTERY) {
dfplayer.playPhysicalTrack(7);
#ifdef DEEP_SLEEP
timeToDeepSleep = millis();
#endif
delay(500);
if (actionMode) {
delay(500);
actionMode = false;
#ifdef LIGHT_EFFECTS
TIMSK2 = 0;
#endif
dfplayer.playPhysicalTrack(soundFont.getPowerOff());
changeMenu = false;
ignition = false;
blasterBlocks = false;
modification = 0;
#ifdef LS_INFO
Serial.println(F("CRITICAL BATTERY !!!"));
#endif
#ifdef LUXEON
lightRetract(ledPins, currentColor, soundFont.getPowerOffTime());
#endif
#ifdef LEDSTRINGS
lightRetract(ledPins, soundFont.getPowerOffTime(),
storage.soundStringPreset[storage.soundFont][1]);
#endif
}
delay(70);
dfplayer.setVolume(15);
delay(70);
dfplayer.playPhysicalTrack(8); // Critical Battery Alert
delay(50);
dfplayer.setSingleLoop(true);
while (lastBatterycheck == lastBatterycheck) {
// volontary infinite loop
// WE WANT to lock the user so HE/SHE MUST change/charge
// his/her battery before ruining it
#ifdef DEEP_SLEEP
timeToDeepSleep = millis() - (2 * SLEEP_TIMER);
deepSleep();
#endif
}
} else if (voltage <= LOW_BATTERY) {
dfplayer.playPhysicalTrack(7); // Low Battery warning
if (actionMode) {
lowBattery = true;
}
delay(1000);
dfplayer.playPhysicalTrack(soundFont.getHum());
delay(40);
if (actionMode) {
dfplayer.setSingleLoop(true);
delay(40);
lowBattery = false;
randomBlink = 0;
blink = 0;
}
}
lastBatterycheck = millis();
#ifdef LS_DEBUG
Serial.print(F("Vcc:"));
Serial.println(readVcc());
#endif
}
// if MPU6050 DMP programming failed, don't try to do anything : EPIC FAIL !
if (!dmpReady) {
return;
}
mainButton.tick();
lockupButton.tick();
/*//////////////////////////////////////////////////////////////////////////////////////////////////////////
* ACTION MODE HANDLER
*/ /////////////////////////////////////////////////////////////////////////////////////////////////////////
if (actionMode) {
/*
// In case we want to time the loop
Serial.print(F("Action Mode"));
Serial.print(F(" time="));
Serial.println(millis());
*/
if (!ignition) {
/*
* This is the very first loop after Action Mode has been turned on
*/
attachInterrupt(0, dmpDataReady, RISING);
// Reduce lockup trigger time for faster lockup response
lockupButton.setPressTicks(PRESS_ACTION);
#ifdef LS_INFO
Serial.println(F("START ACTION"));
#endif
//Play powerons wavs
const uint16_t ignitionID = soundFont.getPowerOn();
dfplayer.playPhysicalTrack(ignitionID);
// Light up the ledstrings
#ifdef LEDSTRINGS
lightIgnition(ledPins, soundFont.getPowerOnTime(),
storage.soundStringPreset[storage.soundFont][0]);
#endif
#ifdef LUXEON
lightIgnition(ledPins, currentColor, soundFont.getPowerOnTime());
#endif
sndSuppress = millis();
#ifdef LIGHT_EFFECTS
/*
* Interrupt Timer2 configuration
*/
OCR2A = 2; // Around 44100 Hz
TCCR2A |= (1 << WGM21); // Set to CTC Mode
// set prescaler to 256
TCCR2B |= (1 << CS21) | (1 << CS22);
// start timer2 compare interrupt:
TIMSK2 |= (1 << OCIE2A);
#endif
// Get the initial position of the motion detector
motionEngine();
ignition = true;
#ifdef ACCENT_LED
// turns accent LED On
analogWrite(ACCENT_LED, HIGH);
#endif
}
// ************************* blade movement detection ************************************
//Let's get our values !
motionEngine();
/*
* CLASH DETECTION :
* A clash is a violent deceleration when 2 blades hit each other
* For a realistic clash detection it's imperative to detect
* such a deceleration instantenously, which is only feasible
* using the motion interrupt feature of the MPU6050.
*/
mpuIntStatus = mpu.getIntStatus();
if (mpuIntStatus > 60 and mpuIntStatus < 70 and not lockup) {
/*
* THIS IS A CLASH !
*/
#ifdef LUXEON
getColor(currentColor, storage.clashColor);
lightOn(ledPins, currentColor);
#endif
#ifdef LS_CLASH_DEBUG
Serial.print(F("CLASH\tmpuIntStatus="));
Serial.println(mpuIntStatus);
#endif
if (millis() - sndSuppress >= 100) {
blink = 0;
clash = CLASH_FLASH_TIME;
dfplayer.playPhysicalTrack(soundFont.getClash());
sndSuppress = millis();
}
}
/*
* SWING DETECTION
* We detect swings as hilt's orientation change
* since IMUs sucks at determining relative position in space
*/
#ifdef BLADE_Z
else if ((millis() - sndSuppress >= SWING_SUPPRESS) and not lockup
and abs(quaternion.w) > storage.swingTreshold and aaWorld.z < 0
and abs(quaternion.z) < (9 / 2) * storage.swingTreshold
and (abs(quaternion.x) > 3 * storage.swingTreshold
or abs(quaternion.y) > 3 * storage.swingTreshold) // We don't want to treat blade Z axe rotations as a swing
)
#endif
#ifdef BLADE_Y
else if ((millis() - sndSuppress >= SWING_SUPPRESS)
and not lockup
and abs(quaternion.w) > storage.swingTreshold
and aaWorld.y < 0
and abs(quaternion.y) < (9 / 2) * storage.swingTreshold
and (abs(quaternion.x) > 3 * storage.swingTreshold
or abs(quaternion.z) > 3 * storage.swingTreshold) // We don't want to treat blade Y axe rotations as a swing
)
#endif
#ifdef BLADE_X
else if ((millis() - sndSuppress >= SWING_SUPPRESS)
and not lockup
and abs(quaternion.w) > storage.swingTreshold
and aaWorld.x < 0
and abs(quaternion.x) < (9 / 2) * storage.swingTreshold
and (abs(quaternion.z) > 3 * storage.swingTreshold
or abs(quaternion.y) > 3 * storage.swingTreshold) // We don't want to treat blade X axe rotations as a swing
)
#endif
{
if (!blasterBlocks) {
/*
* THIS IS A SWING !
*/
#ifdef LS_SWING_DEBUG
Serial.print(F("SWING\ttime="));
Serial.print(millis() - sndSuppress);
Serial.print(F("\t"));
printAcceleration(aaWorld);
printQuaternion(quaternion, 1);
#endif
dfplayer.playPhysicalTrack(soundFont.getSwing());
sndSuppress = millis();
} else {
if (soundFont.getBlaster()) {
#ifdef LEDSTRINGS
blasterPin = random(6); //momentary shut off one led segment
blink = 0;
#endif
#ifdef LUXEON
getColor(currentColor, storage.clashColor);
lightOn(ledPins, currentColor);
#endif
blaster = BLASTER_FLASH_TIME;
// Some Soundfont may not have Blaster sounds
if (millis() - sndSuppress > 50) {
dfplayer.playPhysicalTrack(soundFont.getBlaster());
sndSuppress = millis();
}
}
}
}
#ifdef WRIST_MOVEMENTS
#ifdef BLADE_Z
else if ((millis() - sndSuppress >= SWING_SUPPRESS)
and not lockup
and abs(quaternion.w) > storage.swingTreshold
and (aaWorld.z > 0
and abs(quaternion.z) > (13 / 2) * WRIST_SENSIBILITY
and abs(quaternion.x) < 3 * storage.swingTreshold
and abs(quaternion.y) < 3 * storage.swingTreshold)) // We specifically treat blade Z axe rotations as a swing
#endif
#ifdef BLADE_Y
else if ((millis() - sndSuppress >= SWING_SUPPRESS)
and not lockup
and abs(quaternion.w) > storage.swingTreshold
and (aaWorld.y > 0
and abs(quaternion.y) > (13 / 2) * WRIST_SENSIBILITY
and abs(quaternion.x) < 3 * storage.swingTreshold
and abs(quaternion.z) < 3 * storage.swingTreshold) ) // We specifically treat blade Z axe rotations as a swing
#endif
#ifdef BLADE_X
else if ((millis() - sndSuppress >= SWING_SUPPRESS)
and not lockup
and abs(quaternion.w) > storage.swingTreshold
and (aaWorld.x > 0
and abs(quaternion.x) > (13 / 2) * WRIST_SENSIBILITY
and abs(quaternion.z) < 3 * storage.swingTreshold
and abs(quaternion.y) < 3 * storage.swingTreshold)) // We specifically treat blade Z axe rotations as a swing
#endif
{
/*
* THIS IS A WRIST TWIST !
* The blade did rotate around its own axe
*/
if (soundFont.getWrist()) {
// Some Soundfont may not have Wrist sounds
#ifdef LS_SWING_DEBUG
Serial.print(F("WRIST\ttime="));
Serial.print(millis() - sndSuppress);
Serial.print(F("\t"));
printAcceleration(aaWorld);
printQuaternion(quaternion, 1);
#endif
dfplayer.playPhysicalTrack(soundFont.getWrist());
sndSuppress = millis();
}
}
#endif
// ************************* blade movement detection ends***********************************
} ////END ACTION MODE HANDLER///////////////////////////////////////////////////////////////////////////////////////
/*//////////////////////////////////////////////////////////////////////////////////////////////////////////
* CONFIG MODE HANDLER
*//////////////////////////////////////////////////////////////////////////////////////////////////////////
else if (configMode) {
if (!browsing) {
dfplayer.playPhysicalTrack(3);
delay(600);
#ifdef LS_INFO
Serial.println(F("START CONF"));
#endif
browsing = true;
enterMenu = true;
}
if (modification == -1) {
#ifdef LS_INFO
Serial.print(F("-:"));
#endif
dfplayer.playPhysicalTrack(2);
delay(50);
} else if (modification == 1) {
#ifdef LS_INFO
Serial.print(F("+:"));
#endif
dfplayer.playPhysicalTrack(1);
delay(50);
}
switch (menu) {
case 0: //VOLUME
confMenuStart(storage.volume, 4, dfplayer);
confParseValue(storage.volume, 0, 30, 1, dfplayer);
if (modification) {
modification = 0;
storage.volume = value;
dfplayer.setVolume(storage.volume); // Too Slow: we'll change volume on exit
delay(50);
#ifdef LS_INFO
Serial.println(storage.volume);
#endif
}
break;
/* case 1: // SOUNDFONT
confMenuStart(storage.soundFont, 5, dfplayer);
play = false;
confParseValue(storage.soundFont, 2, SOUNDFONT_QUANTITY + 1, 1,
dfplayer);
if (modification) {
modification = 0;
storage.soundFont = value;
soundFont.setID(value);
dfplayer.playPhysicalTrack(soundFont.getBoot());
delay(150);
#ifdef LUXEON
storage.mainColor = storage.soundFontColorPreset[value][0];
storage.clashColor = storage.soundFontColorPreset[value][1];
#endif
#ifdef LS_INFO
Serial.println(soundFont.getID());
#endif
}
break;
*/
#ifdef LEDSTRINGS
case 2: // POWERON EFFECT
confMenuStart(storage.soundStringPreset[storage.soundFont][0], 17,
dfplayer);
confParseValue(storage.soundStringPreset[storage.soundFont][0], 0,
1, 1, dfplayer);
if (modification) {
modification = 0;
storage.soundStringPreset[storage.soundFont][0] = value;
#ifdef LS_INFO
Serial.println(storage.soundStringPreset[storage.soundFont][0]);
#endif
}
break;
case 3: //POWEROFF EFFECT
confMenuStart(storage.soundStringPreset[storage.soundFont][1], 18,
dfplayer);
confParseValue(storage.soundStringPreset[storage.soundFont][1], 0,
1, 1, dfplayer);
if (modification) {
modification = 0;
storage.soundStringPreset[storage.soundFont][1] = value;
#ifdef LS_INFO
Serial.println(storage.soundStringPreset[storage.soundFont][1]);
#endif
}
break;
case 4: //FLICKER EFFECT
confMenuStart(storage.soundStringPreset[storage.soundFont][2], 19,
dfplayer);
confParseValue(storage.soundStringPreset[storage.soundFont][2], 0,
2, 1, dfplayer);
if (modification) {
modification = 0;
storage.soundStringPreset[storage.soundFont][2] = value;
#ifdef LS_INFO
Serial.println(storage.soundStringPreset[storage.soundFont][2]);
#endif
}
break;
#endif
#ifdef LUXEON
case 2: // BLADE MAIN COLOR
confMenuStart(storage.mainColor, 9, dfplayer);
confParseValue(storage.mainColor, 0, COLORS - 1, 1, dfplayer);
if (modification) {
modification = 0;
storage.mainColor = value;
getColor(currentColor, storage.mainColor);
lightOn(ledPins, currentColor);
#ifdef LS_INFO
Serial.print(storage.mainColor);
Serial.print("\tR:");
Serial.print(currentColor[0]);
Serial.print("\tG:");
Serial.print(currentColor[1]);
Serial.print(" \tB:");
Serial.println(currentColor[2]);
#endif
}
break;
case 3: //BLADE CLASH COLOR
confMenuStart(storage.clashColor, 10, dfplayer);
confParseValue(storage.clashColor, 0, COLORS - 1, 1, dfplayer);
if (modification) {
modification = 0;
storage.clashColor = value;
getColor(currentColor, storage.clashColor);
lightOn(ledPins, currentColor);
#ifdef LS_INFO
Serial.print(storage.clashColor);
Serial.print("\tR:");
Serial.print(currentColor[0]);
Serial.print("\tG:");
Serial.print(currentColor[1]);
Serial.print(" \tB:");
Serial.println(currentColor[2]);
#endif
}
break;
case 4: //SAVE TO SOUNDFONT ?
confMenuStart(0, 11, dfplayer);
if (modification > 0) {
//Yes
//We save color values to this Soundfount
Serial.println(F("Yes"));
dfplayer.playPhysicalTrack(12);
storage.soundFontColorPreset[storage.soundFont][0] =
storage.mainColor;
storage.soundFontColorPreset[storage.soundFont][1] =
storage.clashColor;
menu++;
changeMenu = true;
enterMenu = true;
delay(500);
modification = 0;
} else if (modification < 0) {
//No
// we do nothing and leave this menu
Serial.println(F("No"));
dfplayer.playPhysicalTrack(13);
menu++;
changeMenu = true;
enterMenu = true;
delay(20);
modification = 0;
}
break;
#endif
// case 5: //SWING SENSIBILITY
case 1:
confMenuStart(storage.swingTreshold, 6, dfplayer);
confParseValue(storage.swingTreshold, 500, 3000, -100, dfplayer);
if (modification) {
modification = 0;
storage.swingTreshold = value;
#ifdef LS_INFO
Serial.println(storage.swingTreshold);
#endif
}
break;
default:
menu = 0;
break;
}
} //END CONFIG MODE HANDLER
/*//////////////////////////////////////////////////////////////////////////////////////////////////////////
* STANDBY MODE
*//////////////////////////////////////////////////////////////////////////////////////////////////////////
else if (!actionMode && !configMode) {
if (ignition) { // we just leaved Action Mode
detachInterrupt(0);
#ifdef LIGHT_EFFECTS
TIMSK2 &= ~(1<<OCIE2A);
#endif
dfplayer.playPhysicalTrack(soundFont.getPowerOff());
changeMenu = false;
ignition = false;
blasterBlocks = false;
modification = 0;
#ifdef DEEP_SLEEP
timeToDeepSleep = millis();
#endif
#ifdef LS_INFO
Serial.println(F("END ACTION"));
#endif
lockupButton.setPressTicks(PRESS_CONFIG);
#ifdef LUXEON
lightRetract(ledPins, currentColor, soundFont.getPowerOffTime());
#endif
#ifdef LEDSTRINGS
lightRetract(ledPins, soundFont.getPowerOffTime(),
storage.soundStringPreset[storage.soundFont][1]);
#endif
}
if (browsing) { // we just leaved Config Mode
saveConfig();
lightOff(ledPins);
dfplayer.playPhysicalTrack(3);
browsing = false;
enterMenu = false;
modification = 0;
#ifdef DEEP_SLEEP
timeToDeepSleep = millis();
#endif
//dfplayer.setVolume(storage.volume);
menu = 0;
#ifdef LUXEON
getColor(currentColor, storage.mainColor);
#endif
#ifdef LS_INFO
Serial.println(F("END CONF"));
#endif
}
#ifdef ACCENT_LED
#ifdef HARD_ACCENT
if (millis() - lastAccent <= 400) {
analogWrite(ACCENT_LED, millis() - lastAccent);
} else if (millis() - lastAccent > 400
and millis() - lastAccent <= 800) {
analogWrite(ACCENT_LED, 800 - (millis() - lastAccent));
} else {
lastAccent = millis();
}
#endif
#ifdef SOFT_ACCENT
PWM();
if (millis() - lastAccent >= 7) {
// moved to own funciton for clarity
fadeAccent();
lastAccent = millis();
}
#endif
#endif
#ifdef DEEP_SLEEP
deepSleep();
timeToDeepSleep = millis();
#endif
} // END STANDBY MODE
} //loop
// ====================================================================================
// === POWER MANAGEMENT ===
// ====================================================================================
/*
* DEEP SLEEP FUNCTION
* NEEDS TESTING
* I have difficulties to get out of deep sleep mode
*/
#ifdef DEEP_SLEEP
void deepSleep() {
if (millis() - timeToDeepSleep >= SLEEP_TIMER) {
#ifdef LS_INFO
Serial.println(F("Powersave mode"));
#endif
delay(20);
set_sleep_mode(SLEEP_MODE_PWR_DOWN); // sleep mode is set here
noInterrupts ();
// timed sequence follows
// enables the sleep bit in the mcucr register
sleep_enable()
;
// disable ADC
byte old_ADCSRA = ADCSRA;
ADCSRA = 0;
// turn off various modules
power_all_disable ();
//Reduce processor frequency
byte oldCLKPR = CLKPR;
CLKPR = bit(CLKPCE);
CLKPR = clock_div_256;
// turn off brown-out enable in software
MCUCR = bit (BODS) | bit(BODSE); // turn on brown-out enable select
MCUCR = bit(BODS); // this must be done within 4 clock cycles of above
// guarantees next instruction executed
// sleep within 3 clock cycles of above
interrupts ();
sleep_cpu ()
;
PCIFR |= bit (PCIF0) | bit(PCIF1) | bit(PCIF2); // clear any outstanding interrupts
PCICR |= bit (PCIE0) | bit(PCIE2); // enable pin change interrupts
sleep_mode()
;
// THE PROGRAM CONTINUES FROM HERE AFTER WAKING UP
// cancel sleep as a precaution
sleep_disable()
;
CLKPR = oldCLKPR;
power_all_enable ();
ADCSRA = old_ADCSRA;
CLKPR = bit(CLKPCE);
CLKPR = clock_div_1;
interrupts ();
delay(20);
#ifdef LS_INFO
Serial.println(F("Normal mode"));
#endif
delay(20);
}
}
#endif
// ====================================================================================
// === MOTION DETECTION FUNCTIONS ===
// ====================================================================================
inline void motionEngine() {
long multiplier = 100000;
VectorInt16 aa; // [x, y, z] accel sensor measurements
VectorInt16 aaReal; // [x, y, z] gravity-free accel sensor measurements
VectorFloat gravity; // [x, y, z] gravity vector
// if programming failed, don't try to do anything
if (!dmpReady)
return;
// wait for MPU interrupt or extra packet(s) available
while (!mpuInterrupt && mpuFifoCount < packetSize) {
/* other program behavior stuff here
*
* If you are really paranoid you can frequently test in between other
* stuff to see if mpuInterrupt is true, and if so, "break;" from the
* while() loop to immediately process the MPU data
*/
}
// reset interrupt flag and get INT_STATUS byte
mpuInterrupt = false;
mpuIntStatus = mpu.getIntStatus();
// get current FIFO count
mpuFifoCount = mpu.getFIFOCount();
// check for overflow (this should never happen unless our code is too inefficient)
if ((mpuIntStatus & 0x10) || mpuFifoCount == 1024) {
// reset so we can continue cleanly
mpu.resetFIFO();
// otherwise, check for DMP data ready interrupt (this should happen frequently)
} else if (mpuIntStatus & 0x02) {
// wait for correct available data length, should be a VERY short wait
while (mpuFifoCount < packetSize)
mpuFifoCount = mpu.getFIFOCount();
// read a packet from FIFO
mpu.getFIFOBytes(fifoBuffer, packetSize);
// track FIFO count here in case there is > 1 packet available
// (this lets us immediately read more without waiting for an interrupt)
mpuFifoCount -= packetSize;
//Save last values
quaternion_last = quaternion_reading;
aaWorld_last = aaWorld_reading;
//retrieve values
mpu.dmpGetQuaternion(&quaternion_reading, fifoBuffer);
mpu.dmpGetGravity(&gravity, &quaternion_reading);
mpu.dmpGetAccel(&aa, fifoBuffer);
mpu.dmpGetLinearAccel(&aaReal, &aa, &gravity);
mpu.dmpGetLinearAccelInWorld(&aaWorld, &aaReal, &quaternion_reading);
#ifdef LS_MOTION_HEAVY_DEBUG
// display quaternion values in easy matrix form: w x y z
printQuaternion(quaternion,multiplier);
// display initial world-frame acceleration, adjusted to remove gravity
// and rotated based on known orientation from quaternion
printAcceleration(aaWorld);
#endif
//We multiply by multiplier to obtain a more precise detection
quaternion.w = quaternion_reading.w * multiplier
- quaternion_last.w * multiplier;
quaternion.x = quaternion_reading.x * multiplier
- quaternion_last.x * multiplier;
quaternion.y = quaternion_reading.y * multiplier
- quaternion_last.y * multiplier;
quaternion.z = quaternion_reading.z * multiplier
- quaternion_last.z * multiplier;
}
} //motionEngine
inline void dmpDataReady() {
mpuInterrupt = true;
} //dmpDataReady
#ifdef LS_MOTION_DEBUG
inline void printQuaternion(Quaternion quaternion, long multiplier) {
Serial.print(F("\t\tQ\t\tw="));
Serial.print(quaternion.w * multiplier);
Serial.print(F("\t\tx="));
Serial.print(quaternion.x * multiplier);
Serial.print(F("\t\ty="));
Serial.print(quaternion.y * multiplier);
Serial.print(F("\t\tz="));
Serial.println(quaternion.z * multiplier);
} //printQuaternion
inline void printAcceleration(VectorInt16 aaWorld) {
Serial.print(F("\t\tA\t\tx="));
Serial.print(aaWorld.x);
Serial.print(F("\t\ty="));
Serial.print(aaWorld.y);
Serial.print(F("\t\tz="));
Serial.print(aaWorld.z);
} //printAcceleration
#endif
// ====================================================================================
// === EEPROM MANIPULATION FUNCTIONS ===
// ====================================================================================
inline bool loadConfig() {
bool equals = true;
EEPROM.readBlock(configAdress, storage);
for (uint8_t i = 0; i <= 2; i++) {
if (storage.version[i] != CONFIG_VERSION[i]) {
equals = false;
}
}
Serial.println(storage.version);
return equals;
} //loadConfig
inline void saveConfig() {
EEPROM.updateBlock(configAdress, storage);
} //saveConfig
inline uint16_t readVcc() { // courtesy of Scott Daniels : http://provideyourown.com/2012/secret-arduino-voltmeter-measure-battery-voltage/
// Read 1.1V reference against AVcc
// set the reference to Vcc and the measurement to the internal 1.1V reference
#if defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
ADMUX = _BV(REFS0) | _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
#elif defined (__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__)
ADMUX = _BV(MUX5) | _BV(MUX0);
#elif defined (__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__)
ADMUX = _BV(MUX3) | _BV(MUX2);
#else
ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
#endif
delay(2); // Wait for Vref to settle
ADCSRA |= _BV(ADSC); // Start conversion
while (bit_is_set(ADCSRA, ADSC))
; // measuring
uint8_t low = ADCL; // must read ADCL first - it then locks ADCH
uint8_t high = ADCH; // unlocks both
long result = (high << 8) | low;
result = 1125300L / result; // Calculate Vcc (in mV); 1125300 = 1.1*1023*1000
return result; // Vcc in millivolts
}
// ====================================================================================
// === LED FUNCTIONS ===
// ====================================================================================
#ifdef SOFT_ACCENT
void PWM() {
if (micros() - lastAccentTick >= 8) {
if (pwmPin.state == LOW) {
if (pwmPin.tick >= pwmPin.dutyCycle) {
pwmPin.state = HIGH;
}
} else {
if (pwmPin.tick >= 100 - pwmPin.dutyCycle) {
pwmPin.state = LOW;
pwmPin.tick = 0;
}
}
pwmPin.tick++;
digitalWrite(ACCENT_LED, pwmPin.state);
lastAccentTick = micros();
}
}
void fadeAccent() {
// go through each sw pwm pin, and increase
// the pwm value. this would be like
// calling analogWrite() on each hw pwm pin
if (not pwmPin.revertCycle) {
pwmPin.dutyCycle++;
if (pwmPin.dutyCycle == 100)
pwmPin.revertCycle = true;
} else {
pwmPin.dutyCycle--;
if (pwmPin.dutyCycle == 0)
pwmPin.revertCycle = false;
}
}
#endif
/*
* If no other interrupt has been triggered, and if my calculation are right
* this timer has an almost 44100 khz frequency triggering :
* each 22 µs this method is called and modifies the blade brightness
* The parameter is defined in ignition block
*/
#ifdef LIGHT_EFFECTS
ISR(TIMER2_COMPA_vect, ISR_NOBLOCK) {
#ifdef LEDSTRINGS
lightFlicker(ledPins, storage.soundStringPreset[storage.soundFont][2]);
#endif
#ifdef LUXEON
lightFlicker(ledPins, currentColor,0);
#endif
if (clash) {
if (blink == 0) {
#ifdef LUXEON
getColor(currentColor, storage.clashColor);
lightOn(ledPins, currentColor);
#endif
#ifdef FoCSTRING
FoCOn(FoCSTRING);
#endif
blink++;
} else if (blink < 14) {
blink++;
#ifdef LEDSTRINGS
lightFlicker(ledPins,
storage.soundStringPreset[storage.soundFont][2],
MAX_BRIGHTNESS - (blink / 2));
#endif
#ifdef LUXEON
lightFlicker(ledPins, currentColor, MAX_BRIGHTNESS - (blink / 2));
#endif
} else if (blink == 14) {
#ifdef LUXEON
getColor(currentColor, storage.mainColor);
lightOn(ledPins, currentColor);
#endif
#ifdef FoCSTRING
FoCOff(FoCSTRING);
#endif
blink = 0;
clash = 0;
}
} else if (lockup) {
uint8_t brightness;
if (blink == 0) {
brightness = random(MAX_BRIGHTNESS - 10, MAX_BRIGHTNESS);
randomBlink = random(7, 15);
blink++;
#ifdef FoCSTRING
FoCOn(FoCSTRING);
#endif
#ifdef LUXEON
getColor(currentColor, storage.clashColor);
lightOn(ledPins, currentColor);
#endif
} else if (blink < randomBlink) {
blink++;
} else if (blink == randomBlink and randomBlink >= 14) {
blink = 0;
} else if (blink == randomBlink and randomBlink < 14) {
randomBlink += random(7, 15);
brightness = 0;
#ifdef FoCSTRING
FoCOff(FoCSTRING);
#endif
#ifdef LUXEON
getColor(currentColor, storage.mainColor);
lightOn(ledPins, currentColor);
#endif
}
#ifdef LEDSTRINGS
lightFlicker(ledPins, storage.soundStringPreset[storage.soundFont][2],
brightness);
#endif
#ifdef LUXEON
lightFlicker(ledPins, currentColor, brightness);
#endif
} else if (not lockup && randomBlink != 0) { // We have released lockup button
#ifdef FoCSTRING
FoCOff(FoCSTRING);
#endif
#ifdef LUXEON
getColor(currentColor, storage.mainColor);
lightOn(ledPins, currentColor);
#endif
randomBlink = 0;
} else if (blaster > 0) {
if (blink < 14) {
#ifdef LEDSTRINGS
analogWrite(ledPins[blasterPin], LOW);
#ifdef FoCSTRING
FoCOn(FoCSTRING);
#endif
#endif
#ifdef LUXEON
getColor(currentColor, storage.clashColor);
lightOn(ledPins, currentColor);
#endif
blink++;
}
#ifdef LEDSTRINGS
else if (blink >= 14 and blink < 19) {
lightFlicker(ledPins,
storage.soundStringPreset[storage.soundFont][2]);
if (blasterPin > 0)
analogWrite(ledPins[blasterPin - 1], LOW);
if (blasterPin < 5)
analogWrite(ledPins[blasterPin + 1], LOW);
blink++;
} else if (blink >= 19 and blink < 24) {
#endif
#ifdef LUXEON
else if (blink >= 14 and blink < 24) {
#endif
#ifdef LEDSTRINGS
lightFlicker(ledPins,
storage.soundStringPreset[storage.soundFont][2]);
#endif
#ifdef LUXEON
getColor(currentColor, storage.mainColor);
lightOn(ledPins, currentColor);
#endif
#ifdef FoCSTRING
FoCOff(FoCSTRING);
#endif
blink++;
}
else if (blink == 24) {
#ifdef LUXEON
getColor(currentColor, storage.mainColor);
lightOn(ledPins, currentColor);
#endif
#ifdef FoCSTRING
FoCOff(FoCSTRING);
#endif
#ifdef LEDSTRINGS
lightFlicker(ledPins,
storage.soundStringPreset[storage.soundFont][2]);
#endif
blink = 0;
blaster--;
}
} else if (lowBattery) {
uint8_t brightness;
if (blink == 0) {
randomBlink = random(7, 15);
blink++;
brightness = random(MAX_BRIGHTNESS - 50, MAX_BRIGHTNESS - 70);
} else if (blink < randomBlink) {
blink++;
} else if (blink == randomBlink and randomBlink >= 14) {
blink = 0;
} else if (blink == randomBlink and randomBlink < 14) {
randomBlink += random(7, 15);
brightness = 0;
}
#ifdef LEDSTRINGS
lightFlicker(ledPins, storage.soundStringPreset[storage.soundFont][2],
brightness);
#endif
#ifdef LUXEON
lightFlicker(ledPins, currentColor, brightness);
#endif
}
}
#endif //LIGHT_EFFECTS
The offending statement is on line 184. Library is included on line 13 and PWM channels are defined on line 118 and 119.
Add:
SOFTPWM_DEFINE_OBJECT(2);
after line 119 This macro creates an instance of the CSoftPWM class named PalatisSoftPWM so you should change line 184 back to
PalatisSoftPWM.begin(60);
Hopefully that will solve the problem, let me know how it goes. Looks like a cool project! I'm glad if this library can be of use to you.
I'd be lying if I said I wrote this code though, heh. Just trying to adapt it to support more PWM outputs.
I added SOFTPWM_DEFINE_OBJECT(2); and got different errors:
'CSoftPWM<2u,0u> PalatisSoft PWM' previously declared here (line 1224 @ PalatisSoftPWM.h)
'void __vector_11()' previously defined here (line 1225@ PalatisSoftPWM.h)
make: **** [.ino.cpp.o] Error 1
recipe for target '.ino.cpp.o' failed
redefinition of "CSoftPWM<2u,0u> PalatisSoftPWM' (line 1224 @ PalatisSoftPWM.h)
redefinition of 'void __vector_11()' (line 1225 @ PalatisSoftPWM.h)
Please post the updated code.
/*
* LightSaberOS V1.0
*
* released on: 10 mar 2016
* author: Sebastien CAPOU (neskweek@gmail.com)
* Source : https://github.com/neskweek/LightSaberOS
* Description: Operating System for Arduino based LightSaber
*
* This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
* To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/.
*/
#include <PalatisSoftPWM.h>
#include <Arduino.h>
#include <DFPlayer.h>
#include <I2Cdev.h>
#include <MPU6050_6Axis_MotionApps20.h>
#include <EEPROMex.h>
#include <OneButton.h>
#include <LinkedList.h>
#include <avr/sleep.h>
#include <avr/power.h>
#include "Buttons.h"
#include "Config.h"
#include "ConfigMenu.h"
#include "Light.h"
#include "SoundFont.h"
#if I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE
#include <Wire.h>
#endif
/*
* DO NOT MODIFY
* Unless you know what you're doing
*************************************/
#define CONFIG_VERSION "L01"
#define MEMORYBASE 32
#define SWING_SUPPRESS 700
#ifdef WRIST_MOVEMENTS
#define WRIST_SENSIBILITY 1000
#endif
/************************************/
/*
* BATTERY SAFETY MONITOR
* Modify to match your battery's specs
* rule of thumbs for serial wired batteries:
* LOW_BATTERY = 3200 * Nb_of_battery_cells
* CRITICAL_BATTERY = 3050 * Nb_of_battery_cells
*************************************/
#define LOW_BATTERY 3200
#define CRITICAL_BATTERY 3050
/************************************/
/*
* DEFAULT CONFIG PARAMETERS
* Will be overridden by EEPROM settings
* once the first save will be done
*************************************/
#define VOL 13
#define SOUNDFONT 2
#define SWING 800
/************************************/
/***************************************************************************************************
* Motion detection Variables
*/
MPU6050 mpu;
// MPU control/status vars
volatile bool mpuInterrupt = false; // indicates whether MPU interrupt pin has gone high
bool dmpReady = false; // set true if DMP init was successful
uint8_t mpuIntStatus; // holds actual interrupt status byte from MPU
uint8_t devStatus; // return status after each device operation (0 = success, !0 = error)
uint16_t packetSize; // expected DMP packet size (default is 42 bytes)
uint8_t fifoBuffer[64]; // FIFO storage buffer
uint16_t mpuFifoCount; // count of all bytes currently in FIFO
// orientation/motion vars
Quaternion quaternion; // [w, x, y, z] quaternion container
VectorInt16 aaWorld; // [x, y, z] world-frame accel sensor measurements
static Quaternion quaternion_last; // [w, x, y, z] quaternion container
static Quaternion quaternion_reading; // [w, x, y, z] quaternion container
static VectorInt16 aaWorld_last; // [x, y, z] world-frame accel sensor measurements
static VectorInt16 aaWorld_reading; // [x, y, z] world-frame accel sensor measurements
/***************************************************************************************************
* LED String variables
*/
#ifdef LEDSTRINGS
uint8_t ledPins[] = { LEDSTRING1, LEDSTRING2, LEDSTRING3, LEDSTRING4,
LEDSTRING5, LEDSTRING6 };
uint8_t blasterPin;
#endif
#ifdef LUXEON
uint8_t ledPins[] = {LED_RED, LED_GREEN, LED_BLUE};
uint8_t currentColor[4]; //0:Red 1:Green 2:Blue 3:ColorID
#endif
# ifdef ACCENT_LED
unsigned long lastAccent = millis();
#ifdef SOFT_ACCENT
unsigned long lastAccentTick = micros();
struct softPWM {
uint8_t dutyCycle; // in percent
bool revertCycle;
uint8_t state;
uint16_t tick;
} pwmPin = { 100, false, LOW, 0 };
#endif
#endif
uint8_t blaster = 0;
bool blasterBlocks = false;
uint8_t clash = 0;
bool lockup = false;
uint8_t blink = 0;
uint8_t randomBlink = 0;
SOFTPWM_DEFINE_PINA2_CHANNEL(0);
SOFTPWM_DEFINE_PINA3_CHANNEL(1);
SOFTPWM_DEFINE_OBJECT(2);
/***************************************************************************************************
* Buttons variables
*/
OneButton mainButton(MAIN_BUTTON, true);
OneButton lockupButton(LOCKUP_BUTTON, true);
bool actionMode = false; // Play with your saber
bool configMode = false; // Configure your saber
static bool ignition = false;
static bool browsing = false;
/***************************************************************************************************
* DFPLAYER variables
*/
DFPlayer dfplayer;
SoundFont soundFont;
unsigned long sndSuppress = millis();
/***************************************************************************************************
* ConfigMode Variables
*/
int8_t modification = 0;
int16_t value = 0;
uint8_t menu = 0;
bool enterMenu = false;
bool changeMenu = false;
bool play = false;
unsigned int configAdress = 0;
long lastBatterycheck = 0;
bool lowBattery = false;
#ifdef DEEP_SLEEP
static long timeToDeepSleep = millis();
#endif
volatile uint8_t portbhistory = 0xFF; // default is high because the pull-up
#ifdef LEDSTRINGS
struct StoreStruct {
// This is for mere detection if they are our settings
char version[5];
// The settings
uint8_t volume; // 0 to 30
uint8_t soundFont; // as many Sound font you have defined in Soundfont.h Max:253
uint16_t swingTreshold; // threshold acceleration for Swing
uint8_t soundStringPreset[SOUNDFONT_QUANTITY + 2][3];
} storage;
#endif
#ifdef LUXEON
struct StoreStruct {
// This is for mere detection if they are our settings
char version[5];
// The settings
uint8_t volume;// 0 to 30
uint8_t soundFont;// as many as Sound font you have defined in Soundfont.h Max:253
uint16_t swingTreshold;// treshold acceleration for Swing
uint8_t mainColor;//colorID
uint8_t clashColor;//colorID
uint8_t soundFontColorPreset[SOUNDFONT_QUANTITY + 2][2];//colorIDs
// soundFontColorPreset[sndft][0] : main colorID
// soundFontColorPreset[sndft][1] : clash colorID
}storage;
#endif
// ====================================================================================
// === SETUP ROUTINE ===
// ====================================================================================
void setup() {
PalatisSoftPWM.begin(60);
// join I2C bus (I2Cdev library doesn't do this automatically)
#if I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE
Wire.begin();
TWBR = 24; // 400kHz I2C clock (200kHz if CPU is 8MHz). Comment this line if having compilation difficulties with TWBR.
#elif I2CDEV_IMPLEMENTATION == I2CDEV_BUILTIN_FASTWIRE
Fastwire::setup(400, true);
#endif
#ifdef LS_INFO
// Serial line for debug
Serial.begin(115200);
#endif
#ifdef LS_DEBUG
// Serial line for debug
Serial.begin(115200);
#endif
/***** LOAD CONFIG *****/
// Get config from EEPROM if there is one
// or initialize value with default ones set in StoreStruct
EEPROM.setMemPool(MEMORYBASE, EEPROMSizeATmega328); //Set memorypool base to 32, assume Arduino Uno board
configAdress = EEPROM.getAddress(sizeof(StoreStruct)); // Size of config object
if (!loadConfig()) {
for (uint8_t i = 0; i <= 3; i++)
storage.version[i] = CONFIG_VERSION[i];
storage.soundFont = SOUNDFONT;
storage.volume = VOL;
storage.swingTreshold = SWING;
#ifdef LEDSTRINGS
storage.soundStringPreset[2][0] = 0; //PowerOn
storage.soundStringPreset[2][1] = 0; //PowerOff
storage.soundStringPreset[2][2] = 0; //Flickering
storage.soundStringPreset[3][0] = 1;
storage.soundStringPreset[3][1] = 1;
storage.soundStringPreset[3][2] = 0;
#endif
#ifdef LUXEON
storage.mainColor = 4;
storage.clashColor = 5;
storage.soundFontColorPreset[2][0] = 2;
storage.soundFontColorPreset[2][1] = 0;
storage.soundFontColorPreset[3][0] = 0;
storage.soundFontColorPreset[3][1] = 5;
#endif
#ifdef LS_INFO
Serial.println(F("DEFAULT VALUE"));
#endif
}
#ifdef LS_INFO
else {
Serial.println(F("EEPROM LOADED"));
}
#endif
#ifdef LUXEON
//initialise start color
getColor(currentColor, storage.mainColor);
#endif
/***** LOAD CONFIG *****/
/***** MP6050 MOTION DETECTOR INITIALISATION *****/
// initialize device
#ifdef LS_INFO
Serial.println(F("Initializing I2C devices..."));
#endif
mpu.initialize();
// verify connection
#ifdef LS_INFO
Serial.println(F("Testing device connections..."));
Serial.println(
mpu.testConnection() ?
F("MPU6050 connection successful") :
F("MPU6050 connection failed"));
// load and configure the DMP
Serial.println(F("Initializing DMP..."));
#endif
devStatus = mpu.dmpInitialize();
/*
* Those offsets are specific to each MPU6050 device.
* they are found via calibration process.
* See this script http://www.i2cdevlib.com/forums/index.php?app=core&module=attach§ion=attach&attach_id=27
*/
mpu.setXAccelOffset(-440);
mpu.setYAccelOffset(-699);
mpu.setZAccelOffset(974);
mpu.setXGyroOffset(60);
mpu.setYGyroOffset(-36);
mpu.setZGyroOffset(-63);
// make sure it worked (returns 0 if so)
if (devStatus == 0) {
// turn on the DMP, now that it's ready
#ifdef LS_INFO
Serial.println(F("Enabling DMP..."));
#endif
mpu.setDMPEnabled(true);
// enable Arduino interrupt detection
#ifdef LS_INFO
Serial.println(
F(
"Enabling interrupt detection (Arduino external interrupt 0)..."));
#endif
// attachInterrupt(0, dmpDataReady, RISING);
mpuIntStatus = mpu.getIntStatus();
// set our DMP Ready flag so the main loop() function knows it's okay to use it
#ifdef LS_INFO
Serial.println(F("DMP ready! Waiting for first interrupt..."));
#endif
dmpReady = true;
// get expected DMP packet size for later comparison
packetSize = mpu.dmpGetFIFOPacketSize();
} else {
// ERROR!
// 1 = initial memory load failed
// 2 = DMP configuration updates failed
// (if it's going to break, usually the code will be 1)
#ifdef LS_INFO
Serial.print(F("DMP Initialization failed (code "));
Serial.print(devStatus);
Serial.println(F(")"));
#endif
}
// configure the motion interrupt for clash recognition
// INT_PIN_CFG register
// in the working code of MPU6050_DMP all bits of the INT_PIN_CFG are false (0)
mpu.setInterruptMode(false); // INT_PIN_CFG register INT_LEVEL (0-active high, 1-active low)
mpu.setInterruptDrive(false); // INT_PIN_CFG register INT_OPEN (0-push/pull, 1-open drain)
mpu.setInterruptLatch(false); // INT_PIN_CFG register LATCH_INT_EN (0 - emits 50us pulse upon trigger, 1-pin is held until int is cleared)
mpu.setInterruptLatchClear(false); // INT_PIN_CFG register INT_RD_CLEAR (0-clear int only on reading int status reg, 1-any read clears int)
mpu.setFSyncInterruptLevel(false);
mpu.setFSyncInterruptEnabled(false);
mpu.setI2CBypassEnabled(false);
// Enable/disable interrupt sources - enable only motion interrupt
mpu.setIntFreefallEnabled(false);
mpu.setIntMotionEnabled(true);
mpu.setIntZeroMotionEnabled(false);
mpu.setIntFIFOBufferOverflowEnabled(false);
mpu.setIntI2CMasterEnabled(false);
mpu.setIntDataReadyEnabled(false);
mpu.setIntMotionEnabled(true); // INT_ENABLE register enable interrupt source motion detection
mpu.setMotionDetectionThreshold(10); // 1mg/LSB
mpu.setMotionDetectionDuration(2); // number of consecutive samples above threshold to trigger int
mpuIntStatus = mpu.getIntStatus();
#ifdef LS_CLASH_DEBUG
Serial.println("MPU6050 register setup:");
Serial.print("INT_PIN_CFG\t");
Serial.print(mpu.getInterruptMode());
Serial.print("\t");
Serial.print(mpu.getInterruptDrive());
Serial.print("\t");
Serial.print(mpu.getInterruptLatch());
Serial.print("\t");
Serial.print(mpu.getInterruptLatchClear());
Serial.print("\t");
Serial.print(mpu.getFSyncInterruptLevel());
Serial.print("\t");
Serial.print(mpu.getFSyncInterruptEnabled());
Serial.print("\t");
Serial.println(mpu.getI2CBypassEnabled());
// list INT_ENABLE register contents
Serial.print("INT_ENABLE\t");
Serial.print(mpu.getIntFreefallEnabled());
Serial.print("\t");
Serial.print(mpu.getIntMotionEnabled());
Serial.print("\t");
Serial.print(mpu.getIntZeroMotionEnabled());
Serial.print("\t");
Serial.print(mpu.getIntFIFOBufferOverflowEnabled());
Serial.print("\t");
Serial.print(mpu.getIntI2CMasterEnabled());
Serial.print("\t");
Serial.println(mpu.getIntDataReadyEnabled());
#endif
/***** MP6050 MOTION DETECTOR INITIALISATION *****/
/***** LED SEGMENT INITIALISATION *****/
// initialize ledstring segments
DDRD |= B01101000;
DDRB |= B00001110;
//We shut off all pins that could wearing leds,just to be sure
PORTD &= B10010111;
PORTB &= B11110001;
#ifdef FoCSTRING
pinMode(FoCSTRING, OUTPUT);
FoCOff(FoCSTRING);
#endif
#ifdef ACCENT_LED
pinMode(ACCENT_LED, OUTPUT);
#endif
//Randomize randomness (no really that's what it does)
randomSeed(analogRead(2));
/***** LED SEGMENT INITIALISATION *****/
/***** BUTTONS INITIALISATION *****/
#ifdef DEEP_SLEEP
PCMSK0 = (1 << PCINT4); // set PCINT4 (PIN 12) to trigger an interrupt on state change
PCMSK2 = (1 << PCINT20); // set PCINT20 (PIN 4) to trigger an interrupt on state change
#endif
// link the Main button functions.
mainButton.setClickTicks(CLICK);
mainButton.setPressTicks(PRESS_CONFIG);
mainButton.attachClick(mainClick);
//mainButton.attachDoubleClick(mainDoubleClick);
mainButton.attachLongPressStart(mainLongPressStart);
mainButton.attachLongPressStop(mainLongPressStop);
mainButton.attachDuringLongPress(mainLongPress);
// link the Lockup button functions.
lockupButton.setClickTicks(CLICK);
lockupButton.setPressTicks(PRESS_CONFIG);
lockupButton.attachClick(lockupClick);
//lockupButton.attachDoubleClick(lockupDoubleClick);
lockupButton.attachLongPressStart(lockupLongPressStart);
lockupButton.attachLongPressStop(lockupLongPressStop);
lockupButton.attachDuringLongPress(lockupLongPress);
/***** BUTTONS INITIALISATION *****/
/***** DF PLAYER INITIALISATION *****/
dfplayer.setSerial(DFPLAYER_TX, DFPLAYER_RX);
dfplayer.setVolume(storage.volume);
delay(200);
pinMode(SPK1, INPUT);
pinMode(SPK2, INPUT);
soundFont.setID(storage.soundFont);
/***** DF PLAYER INITIALISATION *****/
//setup finished. Boot ready. We notify !
dfplayer.playPhysicalTrack(16);
delay(20);
}
// ====================================================================================
// === LOOP ROUTINE ===
// ====================================================================================
void loop() {
//Check battery level
if (lastBatterycheck == 0 or millis() - lastBatterycheck >= 60000) {
uint16_t voltage = readVcc();
if (voltage <= CRITICAL_BATTERY) {
dfplayer.playPhysicalTrack(7);
#ifdef DEEP_SLEEP
timeToDeepSleep = millis();
#endif
delay(500);
if (actionMode) {
delay(500);
actionMode = false;
#ifdef LIGHT_EFFECTS
TIMSK2 = 0;
#endif
dfplayer.playPhysicalTrack(soundFont.getPowerOff());
changeMenu = false;
ignition = false;
blasterBlocks = false;
modification = 0;
#ifdef LS_INFO
Serial.println(F("CRITICAL BATTERY !!!"));
#endif
#ifdef LUXEON
lightRetract(ledPins, currentColor, soundFont.getPowerOffTime());
#endif
#ifdef LEDSTRINGS
lightRetract(ledPins, soundFont.getPowerOffTime(),
storage.soundStringPreset[storage.soundFont][1]);
#endif
}
delay(70);
dfplayer.setVolume(15);
delay(70);
dfplayer.playPhysicalTrack(8); // Critical Battery Alert
delay(50);
dfplayer.setSingleLoop(true);
while (lastBatterycheck == lastBatterycheck) {
// volontary infinite loop
// WE WANT to lock the user so HE/SHE MUST change/charge
// his/her battery before ruining it
#ifdef DEEP_SLEEP
timeToDeepSleep = millis() - (2 * SLEEP_TIMER);
deepSleep();
#endif
}
} else if (voltage <= LOW_BATTERY) {
dfplayer.playPhysicalTrack(7); // Low Battery warning
if (actionMode) {
lowBattery = true;
}
delay(1000);
dfplayer.playPhysicalTrack(soundFont.getHum());
delay(40);
if (actionMode) {
dfplayer.setSingleLoop(true);
delay(40);
lowBattery = false;
randomBlink = 0;
blink = 0;
}
}
lastBatterycheck = millis();
#ifdef LS_DEBUG
Serial.print(F("Vcc:"));
Serial.println(readVcc());
#endif
}
// if MPU6050 DMP programming failed, don't try to do anything : EPIC FAIL !
if (!dmpReady) {
return;
}
mainButton.tick();
lockupButton.tick();
/*//////////////////////////////////////////////////////////////////////////////////////////////////////////
* ACTION MODE HANDLER
*/ /////////////////////////////////////////////////////////////////////////////////////////////////////////
if (actionMode) {
/*
// In case we want to time the loop
Serial.print(F("Action Mode"));
Serial.print(F(" time="));
Serial.println(millis());
*/
if (!ignition) {
/*
* This is the very first loop after Action Mode has been turned on
*/
attachInterrupt(0, dmpDataReady, RISING);
// Reduce lockup trigger time for faster lockup response
lockupButton.setPressTicks(PRESS_ACTION);
#ifdef LS_INFO
Serial.println(F("START ACTION"));
#endif
//Play powerons wavs
const uint16_t ignitionID = soundFont.getPowerOn();
dfplayer.playPhysicalTrack(ignitionID);
// Light up the ledstrings
#ifdef LEDSTRINGS
lightIgnition(ledPins, soundFont.getPowerOnTime(),
storage.soundStringPreset[storage.soundFont][0]);
#endif
#ifdef LUXEON
lightIgnition(ledPins, currentColor, soundFont.getPowerOnTime());
#endif
sndSuppress = millis();
#ifdef LIGHT_EFFECTS
/*
* Interrupt Timer2 configuration
*/
OCR2A = 2; // Around 44100 Hz
TCCR2A |= (1 << WGM21); // Set to CTC Mode
// set prescaler to 256
TCCR2B |= (1 << CS21) | (1 << CS22);
// start timer2 compare interrupt:
TIMSK2 |= (1 << OCIE2A);
#endif
// Get the initial position of the motion detector
motionEngine();
ignition = true;
#ifdef ACCENT_LED
// turns accent LED On
analogWrite(ACCENT_LED, HIGH);
#endif
}
// ************************* blade movement detection ************************************
//Let's get our values !
motionEngine();
/*
* CLASH DETECTION :
* A clash is a violent deceleration when 2 blades hit each other
* For a realistic clash detection it's imperative to detect
* such a deceleration instantenously, which is only feasible
* using the motion interrupt feature of the MPU6050.
*/
mpuIntStatus = mpu.getIntStatus();
if (mpuIntStatus > 60 and mpuIntStatus < 70 and not lockup) {
/*
* THIS IS A CLASH !
*/
#ifdef LUXEON
getColor(currentColor, storage.clashColor);
lightOn(ledPins, currentColor);
#endif
#ifdef LS_CLASH_DEBUG
Serial.print(F("CLASH\tmpuIntStatus="));
Serial.println(mpuIntStatus);
#endif
if (millis() - sndSuppress >= 100) {
blink = 0;
clash = CLASH_FLASH_TIME;
dfplayer.playPhysicalTrack(soundFont.getClash());
sndSuppress = millis();
}
}
/*
* SWING DETECTION
* We detect swings as hilt's orientation change
* since IMUs sucks at determining relative position in space
*/
#ifdef BLADE_Z
else if ((millis() - sndSuppress >= SWING_SUPPRESS) and not lockup
and abs(quaternion.w) > storage.swingTreshold and aaWorld.z < 0
and abs(quaternion.z) < (9 / 2) * storage.swingTreshold
and (abs(quaternion.x) > 3 * storage.swingTreshold
or abs(quaternion.y) > 3 * storage.swingTreshold) // We don't want to treat blade Z axe rotations as a swing
)
#endif
#ifdef BLADE_Y
else if ((millis() - sndSuppress >= SWING_SUPPRESS)
and not lockup
and abs(quaternion.w) > storage.swingTreshold
and aaWorld.y < 0
and abs(quaternion.y) < (9 / 2) * storage.swingTreshold
and (abs(quaternion.x) > 3 * storage.swingTreshold
or abs(quaternion.z) > 3 * storage.swingTreshold) // We don't want to treat blade Y axe rotations as a swing
)
#endif
#ifdef BLADE_X
else if ((millis() - sndSuppress >= SWING_SUPPRESS)
and not lockup
and abs(quaternion.w) > storage.swingTreshold
and aaWorld.x < 0
and abs(quaternion.x) < (9 / 2) * storage.swingTreshold
and (abs(quaternion.z) > 3 * storage.swingTreshold
or abs(quaternion.y) > 3 * storage.swingTreshold) // We don't want to treat blade X axe rotations as a swing
)
#endif
{
if (!blasterBlocks) {
/*
* THIS IS A SWING !
*/
#ifdef LS_SWING_DEBUG
Serial.print(F("SWING\ttime="));
Serial.print(millis() - sndSuppress);
Serial.print(F("\t"));
printAcceleration(aaWorld);
printQuaternion(quaternion, 1);
#endif
dfplayer.playPhysicalTrack(soundFont.getSwing());
sndSuppress = millis();
} else {
if (soundFont.getBlaster()) {
#ifdef LEDSTRINGS
blasterPin = random(6); //momentary shut off one led segment
blink = 0;
#endif
#ifdef LUXEON
getColor(currentColor, storage.clashColor);
lightOn(ledPins, currentColor);
#endif
blaster = BLASTER_FLASH_TIME;
// Some Soundfont may not have Blaster sounds
if (millis() - sndSuppress > 50) {
dfplayer.playPhysicalTrack(soundFont.getBlaster());
sndSuppress = millis();
}
}
}
}
#ifdef WRIST_MOVEMENTS
#ifdef BLADE_Z
else if ((millis() - sndSuppress >= SWING_SUPPRESS)
and not lockup
and abs(quaternion.w) > storage.swingTreshold
and (aaWorld.z > 0
and abs(quaternion.z) > (13 / 2) * WRIST_SENSIBILITY
and abs(quaternion.x) < 3 * storage.swingTreshold
and abs(quaternion.y) < 3 * storage.swingTreshold)) // We specifically treat blade Z axe rotations as a swing
#endif
#ifdef BLADE_Y
else if ((millis() - sndSuppress >= SWING_SUPPRESS)
and not lockup
and abs(quaternion.w) > storage.swingTreshold
and (aaWorld.y > 0
and abs(quaternion.y) > (13 / 2) * WRIST_SENSIBILITY
and abs(quaternion.x) < 3 * storage.swingTreshold
and abs(quaternion.z) < 3 * storage.swingTreshold) ) // We specifically treat blade Z axe rotations as a swing
#endif
#ifdef BLADE_X
else if ((millis() - sndSuppress >= SWING_SUPPRESS)
and not lockup
and abs(quaternion.w) > storage.swingTreshold
and (aaWorld.x > 0
and abs(quaternion.x) > (13 / 2) * WRIST_SENSIBILITY
and abs(quaternion.z) < 3 * storage.swingTreshold
and abs(quaternion.y) < 3 * storage.swingTreshold)) // We specifically treat blade Z axe rotations as a swing
#endif
{
/*
* THIS IS A WRIST TWIST !
* The blade did rotate around its own axe
*/
if (soundFont.getWrist()) {
// Some Soundfont may not have Wrist sounds
#ifdef LS_SWING_DEBUG
Serial.print(F("WRIST\ttime="));
Serial.print(millis() - sndSuppress);
Serial.print(F("\t"));
printAcceleration(aaWorld);
printQuaternion(quaternion, 1);
#endif
dfplayer.playPhysicalTrack(soundFont.getWrist());
sndSuppress = millis();
}
}
#endif
// ************************* blade movement detection ends***********************************
} ////END ACTION MODE HANDLER///////////////////////////////////////////////////////////////////////////////////////
/*//////////////////////////////////////////////////////////////////////////////////////////////////////////
* CONFIG MODE HANDLER
*//////////////////////////////////////////////////////////////////////////////////////////////////////////
else if (configMode) {
if (!browsing) {
dfplayer.playPhysicalTrack(3);
delay(600);
#ifdef LS_INFO
Serial.println(F("START CONF"));
#endif
browsing = true;
enterMenu = true;
}
if (modification == -1) {
#ifdef LS_INFO
Serial.print(F("-:"));
#endif
dfplayer.playPhysicalTrack(2);
delay(50);
} else if (modification == 1) {
#ifdef LS_INFO
Serial.print(F("+:"));
#endif
dfplayer.playPhysicalTrack(1);
delay(50);
}
switch (menu) {
case 0: //VOLUME
confMenuStart(storage.volume, 4, dfplayer);
confParseValue(storage.volume, 0, 30, 1, dfplayer);
if (modification) {
modification = 0;
storage.volume = value;
dfplayer.setVolume(storage.volume); // Too Slow: we'll change volume on exit
delay(50);
#ifdef LS_INFO
Serial.println(storage.volume);
#endif
}
break;
/* case 1: // SOUNDFONT
confMenuStart(storage.soundFont, 5, dfplayer);
play = false;
confParseValue(storage.soundFont, 2, SOUNDFONT_QUANTITY + 1, 1,
dfplayer);
if (modification) {
modification = 0;
storage.soundFont = value;
soundFont.setID(value);
dfplayer.playPhysicalTrack(soundFont.getBoot());
delay(150);
#ifdef LUXEON
storage.mainColor = storage.soundFontColorPreset[value][0];
storage.clashColor = storage.soundFontColorPreset[value][1];
#endif
#ifdef LS_INFO
Serial.println(soundFont.getID());
#endif
}
break;
*/
#ifdef LEDSTRINGS
case 2: // POWERON EFFECT
confMenuStart(storage.soundStringPreset[storage.soundFont][0], 17,
dfplayer);
confParseValue(storage.soundStringPreset[storage.soundFont][0], 0,
1, 1, dfplayer);
if (modification) {
modification = 0;
storage.soundStringPreset[storage.soundFont][0] = value;
#ifdef LS_INFO
Serial.println(storage.soundStringPreset[storage.soundFont][0]);
#endif
}
break;
case 3: //POWEROFF EFFECT
confMenuStart(storage.soundStringPreset[storage.soundFont][1], 18,
dfplayer);
confParseValue(storage.soundStringPreset[storage.soundFont][1], 0,
1, 1, dfplayer);
if (modification) {
modification = 0;
storage.soundStringPreset[storage.soundFont][1] = value;
#ifdef LS_INFO
Serial.println(storage.soundStringPreset[storage.soundFont][1]);
#endif
}
break;
case 4: //FLICKER EFFECT
confMenuStart(storage.soundStringPreset[storage.soundFont][2], 19,
dfplayer);
confParseValue(storage.soundStringPreset[storage.soundFont][2], 0,
2, 1, dfplayer);
if (modification) {
modification = 0;
storage.soundStringPreset[storage.soundFont][2] = value;
#ifdef LS_INFO
Serial.println(storage.soundStringPreset[storage.soundFont][2]);
#endif
}
break;
#endif
#ifdef LUXEON
case 2: // BLADE MAIN COLOR
confMenuStart(storage.mainColor, 9, dfplayer);
confParseValue(storage.mainColor, 0, COLORS - 1, 1, dfplayer);
if (modification) {
modification = 0;
storage.mainColor = value;
getColor(currentColor, storage.mainColor);
lightOn(ledPins, currentColor);
#ifdef LS_INFO
Serial.print(storage.mainColor);
Serial.print("\tR:");
Serial.print(currentColor[0]);
Serial.print("\tG:");
Serial.print(currentColor[1]);
Serial.print(" \tB:");
Serial.println(currentColor[2]);
#endif
}
break;
case 3: //BLADE CLASH COLOR
confMenuStart(storage.clashColor, 10, dfplayer);
confParseValue(storage.clashColor, 0, COLORS - 1, 1, dfplayer);
if (modification) {
modification = 0;
storage.clashColor = value;
getColor(currentColor, storage.clashColor);
lightOn(ledPins, currentColor);
#ifdef LS_INFO
Serial.print(storage.clashColor);
Serial.print("\tR:");
Serial.print(currentColor[0]);
Serial.print("\tG:");
Serial.print(currentColor[1]);
Serial.print(" \tB:");
Serial.println(currentColor[2]);
#endif
}
break;
case 4: //SAVE TO SOUNDFONT ?
confMenuStart(0, 11, dfplayer);
if (modification > 0) {
//Yes
//We save color values to this Soundfount
Serial.println(F("Yes"));
dfplayer.playPhysicalTrack(12);
storage.soundFontColorPreset[storage.soundFont][0] =
storage.mainColor;
storage.soundFontColorPreset[storage.soundFont][1] =
storage.clashColor;
menu++;
changeMenu = true;
enterMenu = true;
delay(500);
modification = 0;
} else if (modification < 0) {
//No
// we do nothing and leave this menu
Serial.println(F("No"));
dfplayer.playPhysicalTrack(13);
menu++;
changeMenu = true;
enterMenu = true;
delay(20);
modification = 0;
}
break;
#endif
// case 5: //SWING SENSIBILITY
case 1:
confMenuStart(storage.swingTreshold, 6, dfplayer);
confParseValue(storage.swingTreshold, 500, 3000, -100, dfplayer);
if (modification) {
modification = 0;
storage.swingTreshold = value;
#ifdef LS_INFO
Serial.println(storage.swingTreshold);
#endif
}
break;
default:
menu = 0;
break;
}
} //END CONFIG MODE HANDLER
/*//////////////////////////////////////////////////////////////////////////////////////////////////////////
* STANDBY MODE
*//////////////////////////////////////////////////////////////////////////////////////////////////////////
else if (!actionMode && !configMode) {
if (ignition) { // we just leaved Action Mode
detachInterrupt(0);
#ifdef LIGHT_EFFECTS
TIMSK2 &= ~(1<<OCIE2A);
#endif
dfplayer.playPhysicalTrack(soundFont.getPowerOff());
changeMenu = false;
ignition = false;
blasterBlocks = false;
modification = 0;
#ifdef DEEP_SLEEP
timeToDeepSleep = millis();
#endif
#ifdef LS_INFO
Serial.println(F("END ACTION"));
#endif
lockupButton.setPressTicks(PRESS_CONFIG);
#ifdef LUXEON
lightRetract(ledPins, currentColor, soundFont.getPowerOffTime());
#endif
#ifdef LEDSTRINGS
lightRetract(ledPins, soundFont.getPowerOffTime(),
storage.soundStringPreset[storage.soundFont][1]);
#endif
}
if (browsing) { // we just leaved Config Mode
saveConfig();
lightOff(ledPins);
dfplayer.playPhysicalTrack(3);
browsing = false;
enterMenu = false;
modification = 0;
#ifdef DEEP_SLEEP
timeToDeepSleep = millis();
#endif
//dfplayer.setVolume(storage.volume);
menu = 0;
#ifdef LUXEON
getColor(currentColor, storage.mainColor);
#endif
#ifdef LS_INFO
Serial.println(F("END CONF"));
#endif
}
#ifdef ACCENT_LED
#ifdef HARD_ACCENT
if (millis() - lastAccent <= 400) {
analogWrite(ACCENT_LED, millis() - lastAccent);
} else if (millis() - lastAccent > 400
and millis() - lastAccent <= 800) {
analogWrite(ACCENT_LED, 800 - (millis() - lastAccent));
} else {
lastAccent = millis();
}
#endif
#ifdef SOFT_ACCENT
PWM();
if (millis() - lastAccent >= 7) {
// moved to own funciton for clarity
fadeAccent();
lastAccent = millis();
}
#endif
#endif
#ifdef DEEP_SLEEP
deepSleep();
timeToDeepSleep = millis();
#endif
} // END STANDBY MODE
} //loop
// ====================================================================================
// === POWER MANAGEMENT ===
// ====================================================================================
/*
* DEEP SLEEP FUNCTION
* NEEDS TESTING
* I have difficulties to get out of deep sleep mode
*/
#ifdef DEEP_SLEEP
void deepSleep() {
if (millis() - timeToDeepSleep >= SLEEP_TIMER) {
#ifdef LS_INFO
Serial.println(F("Powersave mode"));
#endif
delay(20);
set_sleep_mode(SLEEP_MODE_PWR_DOWN); // sleep mode is set here
noInterrupts ();
// timed sequence follows
// enables the sleep bit in the mcucr register
sleep_enable()
;
// disable ADC
byte old_ADCSRA = ADCSRA;
ADCSRA = 0;
// turn off various modules
power_all_disable ();
//Reduce processor frequency
byte oldCLKPR = CLKPR;
CLKPR = bit(CLKPCE);
CLKPR = clock_div_256;
// turn off brown-out enable in software
MCUCR = bit (BODS) | bit(BODSE); // turn on brown-out enable select
MCUCR = bit(BODS); // this must be done within 4 clock cycles of above
// guarantees next instruction executed
// sleep within 3 clock cycles of above
interrupts ();
sleep_cpu ()
;
PCIFR |= bit (PCIF0) | bit(PCIF1) | bit(PCIF2); // clear any outstanding interrupts
PCICR |= bit (PCIE0) | bit(PCIE2); // enable pin change interrupts
sleep_mode()
;
// THE PROGRAM CONTINUES FROM HERE AFTER WAKING UP
// cancel sleep as a precaution
sleep_disable()
;
CLKPR = oldCLKPR;
power_all_enable ();
ADCSRA = old_ADCSRA;
CLKPR = bit(CLKPCE);
CLKPR = clock_div_1;
interrupts ();
delay(20);
#ifdef LS_INFO
Serial.println(F("Normal mode"));
#endif
delay(20);
}
}
#endif
// ====================================================================================
// === MOTION DETECTION FUNCTIONS ===
// ====================================================================================
inline void motionEngine() {
long multiplier = 100000;
VectorInt16 aa; // [x, y, z] accel sensor measurements
VectorInt16 aaReal; // [x, y, z] gravity-free accel sensor measurements
VectorFloat gravity; // [x, y, z] gravity vector
// if programming failed, don't try to do anything
if (!dmpReady)
return;
// wait for MPU interrupt or extra packet(s) available
while (!mpuInterrupt && mpuFifoCount < packetSize) {
/* other program behavior stuff here
*
* If you are really paranoid you can frequently test in between other
* stuff to see if mpuInterrupt is true, and if so, "break;" from the
* while() loop to immediately process the MPU data
*/
}
// reset interrupt flag and get INT_STATUS byte
mpuInterrupt = false;
mpuIntStatus = mpu.getIntStatus();
// get current FIFO count
mpuFifoCount = mpu.getFIFOCount();
// check for overflow (this should never happen unless our code is too inefficient)
if ((mpuIntStatus & 0x10) || mpuFifoCount == 1024) {
// reset so we can continue cleanly
mpu.resetFIFO();
// otherwise, check for DMP data ready interrupt (this should happen frequently)
} else if (mpuIntStatus & 0x02) {
// wait for correct available data length, should be a VERY short wait
while (mpuFifoCount < packetSize)
mpuFifoCount = mpu.getFIFOCount();
// read a packet from FIFO
mpu.getFIFOBytes(fifoBuffer, packetSize);
// track FIFO count here in case there is > 1 packet available
// (this lets us immediately read more without waiting for an interrupt)
mpuFifoCount -= packetSize;
//Save last values
quaternion_last = quaternion_reading;
aaWorld_last = aaWorld_reading;
//retrieve values
mpu.dmpGetQuaternion(&quaternion_reading, fifoBuffer);
mpu.dmpGetGravity(&gravity, &quaternion_reading);
mpu.dmpGetAccel(&aa, fifoBuffer);
mpu.dmpGetLinearAccel(&aaReal, &aa, &gravity);
mpu.dmpGetLinearAccelInWorld(&aaWorld, &aaReal, &quaternion_reading);
#ifdef LS_MOTION_HEAVY_DEBUG
// display quaternion values in easy matrix form: w x y z
printQuaternion(quaternion,multiplier);
// display initial world-frame acceleration, adjusted to remove gravity
// and rotated based on known orientation from quaternion
printAcceleration(aaWorld);
#endif
//We multiply by multiplier to obtain a more precise detection
quaternion.w = quaternion_reading.w * multiplier
- quaternion_last.w * multiplier;
quaternion.x = quaternion_reading.x * multiplier
- quaternion_last.x * multiplier;
quaternion.y = quaternion_reading.y * multiplier
- quaternion_last.y * multiplier;
quaternion.z = quaternion_reading.z * multiplier
- quaternion_last.z * multiplier;
}
} //motionEngine
inline void dmpDataReady() {
mpuInterrupt = true;
} //dmpDataReady
#ifdef LS_MOTION_DEBUG
inline void printQuaternion(Quaternion quaternion, long multiplier) {
Serial.print(F("\t\tQ\t\tw="));
Serial.print(quaternion.w * multiplier);
Serial.print(F("\t\tx="));
Serial.print(quaternion.x * multiplier);
Serial.print(F("\t\ty="));
Serial.print(quaternion.y * multiplier);
Serial.print(F("\t\tz="));
Serial.println(quaternion.z * multiplier);
} //printQuaternion
inline void printAcceleration(VectorInt16 aaWorld) {
Serial.print(F("\t\tA\t\tx="));
Serial.print(aaWorld.x);
Serial.print(F("\t\ty="));
Serial.print(aaWorld.y);
Serial.print(F("\t\tz="));
Serial.print(aaWorld.z);
} //printAcceleration
#endif
// ====================================================================================
// === EEPROM MANIPULATION FUNCTIONS ===
// ====================================================================================
inline bool loadConfig() {
bool equals = true;
EEPROM.readBlock(configAdress, storage);
for (uint8_t i = 0; i <= 2; i++) {
if (storage.version[i] != CONFIG_VERSION[i]) {
equals = false;
}
}
Serial.println(storage.version);
return equals;
} //loadConfig
inline void saveConfig() {
EEPROM.updateBlock(configAdress, storage);
} //saveConfig
inline uint16_t readVcc() { // courtesy of Scott Daniels : http://provideyourown.com/2012/secret-arduino-voltmeter-measure-battery-voltage/
// Read 1.1V reference against AVcc
// set the reference to Vcc and the measurement to the internal 1.1V reference
#if defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
ADMUX = _BV(REFS0) | _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
#elif defined (__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__)
ADMUX = _BV(MUX5) | _BV(MUX0);
#elif defined (__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__)
ADMUX = _BV(MUX3) | _BV(MUX2);
#else
ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
#endif
delay(2); // Wait for Vref to settle
ADCSRA |= _BV(ADSC); // Start conversion
while (bit_is_set(ADCSRA, ADSC))
; // measuring
uint8_t low = ADCL; // must read ADCL first - it then locks ADCH
uint8_t high = ADCH; // unlocks both
long result = (high << 8) | low;
result = 1125300L / result; // Calculate Vcc (in mV); 1125300 = 1.1*1023*1000
return result; // Vcc in millivolts
}
// ====================================================================================
// === LED FUNCTIONS ===
// ====================================================================================
#ifdef SOFT_ACCENT
void PWM() {
if (micros() - lastAccentTick >= 8) {
if (pwmPin.state == LOW) {
if (pwmPin.tick >= pwmPin.dutyCycle) {
pwmPin.state = HIGH;
}
} else {
if (pwmPin.tick >= 100 - pwmPin.dutyCycle) {
pwmPin.state = LOW;
pwmPin.tick = 0;
}
}
pwmPin.tick++;
digitalWrite(ACCENT_LED, pwmPin.state);
lastAccentTick = micros();
}
}
void fadeAccent() {
// go through each sw pwm pin, and increase
// the pwm value. this would be like
// calling analogWrite() on each hw pwm pin
if (not pwmPin.revertCycle) {
pwmPin.dutyCycle++;
if (pwmPin.dutyCycle == 100)
pwmPin.revertCycle = true;
} else {
pwmPin.dutyCycle--;
if (pwmPin.dutyCycle == 0)
pwmPin.revertCycle = false;
}
}
#endif
/*
* If no other interrupt has been triggered, and if my calculation are right
* this timer has an almost 44100 khz frequency triggering :
* each 22 µs this method is called and modifies the blade brightness
* The parameter is defined in ignition block
*/
#ifdef LIGHT_EFFECTS
ISR(TIMER2_COMPA_vect, ISR_NOBLOCK) {
#ifdef LEDSTRINGS
lightFlicker(ledPins, storage.soundStringPreset[storage.soundFont][2]);
#endif
#ifdef LUXEON
lightFlicker(ledPins, currentColor,0);
#endif
if (clash) {
if (blink == 0) {
#ifdef LUXEON
getColor(currentColor, storage.clashColor);
lightOn(ledPins, currentColor);
#endif
#ifdef FoCSTRING
FoCOn(FoCSTRING);
#endif
blink++;
} else if (blink < 14) {
blink++;
#ifdef LEDSTRINGS
lightFlicker(ledPins,
storage.soundStringPreset[storage.soundFont][2],
MAX_BRIGHTNESS - (blink / 2));
#endif
#ifdef LUXEON
lightFlicker(ledPins, currentColor, MAX_BRIGHTNESS - (blink / 2));
#endif
} else if (blink == 14) {
#ifdef LUXEON
getColor(currentColor, storage.mainColor);
lightOn(ledPins, currentColor);
#endif
#ifdef FoCSTRING
FoCOff(FoCSTRING);
#endif
blink = 0;
clash = 0;
}
} else if (lockup) {
uint8_t brightness;
if (blink == 0) {
brightness = random(MAX_BRIGHTNESS - 10, MAX_BRIGHTNESS);
randomBlink = random(7, 15);
blink++;
#ifdef FoCSTRING
FoCOn(FoCSTRING);
#endif
#ifdef LUXEON
getColor(currentColor, storage.clashColor);
lightOn(ledPins, currentColor);
#endif
} else if (blink < randomBlink) {
blink++;
} else if (blink == randomBlink and randomBlink >= 14) {
blink = 0;
} else if (blink == randomBlink and randomBlink < 14) {
randomBlink += random(7, 15);
brightness = 0;
#ifdef FoCSTRING
FoCOff(FoCSTRING);
#endif
#ifdef LUXEON
getColor(currentColor, storage.mainColor);
lightOn(ledPins, currentColor);
#endif
}
#ifdef LEDSTRINGS
lightFlicker(ledPins, storage.soundStringPreset[storage.soundFont][2],
brightness);
#endif
#ifdef LUXEON
lightFlicker(ledPins, currentColor, brightness);
#endif
} else if (not lockup && randomBlink != 0) { // We have released lockup button
#ifdef FoCSTRING
FoCOff(FoCSTRING);
#endif
#ifdef LUXEON
getColor(currentColor, storage.mainColor);
lightOn(ledPins, currentColor);
#endif
randomBlink = 0;
} else if (blaster > 0) {
if (blink < 14) {
#ifdef LEDSTRINGS
analogWrite(ledPins[blasterPin], LOW);
#ifdef FoCSTRING
FoCOn(FoCSTRING);
#endif
#endif
#ifdef LUXEON
getColor(currentColor, storage.clashColor);
lightOn(ledPins, currentColor);
#endif
blink++;
}
#ifdef LEDSTRINGS
else if (blink >= 14 and blink < 19) {
lightFlicker(ledPins,
storage.soundStringPreset[storage.soundFont][2]);
if (blasterPin > 0)
analogWrite(ledPins[blasterPin - 1], LOW);
if (blasterPin < 5)
analogWrite(ledPins[blasterPin + 1], LOW);
blink++;
} else if (blink >= 19 and blink < 24) {
#endif
#ifdef LUXEON
else if (blink >= 14 and blink < 24) {
#endif
#ifdef LEDSTRINGS
lightFlicker(ledPins,
storage.soundStringPreset[storage.soundFont][2]);
#endif
#ifdef LUXEON
getColor(currentColor, storage.mainColor);
lightOn(ledPins, currentColor);
#endif
#ifdef FoCSTRING
FoCOff(FoCSTRING);
#endif
blink++;
}
else if (blink == 24) {
#ifdef LUXEON
getColor(currentColor, storage.mainColor);
lightOn(ledPins, currentColor);
#endif
#ifdef FoCSTRING
FoCOff(FoCSTRING);
#endif
#ifdef LEDSTRINGS
lightFlicker(ledPins,
storage.soundStringPreset[storage.soundFont][2]);
#endif
blink = 0;
blaster--;
}
} else if (lowBattery) {
uint8_t brightness;
if (blink == 0) {
randomBlink = random(7, 15);
blink++;
brightness = random(MAX_BRIGHTNESS - 50, MAX_BRIGHTNESS - 70);
} else if (blink < randomBlink) {
blink++;
} else if (blink == randomBlink and randomBlink >= 14) {
blink = 0;
} else if (blink == randomBlink and randomBlink < 14) {
randomBlink += random(7, 15);
brightness = 0;
}
#ifdef LEDSTRINGS
lightFlicker(ledPins, storage.soundStringPreset[storage.soundFont][2],
brightness);
#endif
#ifdef LUXEON
lightFlicker(ledPins, currentColor, brightness);
#endif
}
}
#endif //LIGHT_EFFECTS
Well I'm stumped. That error indicates there are multiple calls to SOFTPWM_DEFINE_OBJECT()
but your code only has one. I just compiled it in the Arduino IDE and it has no errors there. I'm at a bit of a disadvantage here because I don't have Eclipse installed.
Try compiling examples/PalatisSoftPWM_example.ino and let me know how that goes.
Huh, works perfectly using Arduino IDE. Must be an eclipse-specific issue. Will try reinstalling.
@772pilot did you ever find out how to get it working with Eclipse?
Actually yes! I'm not sure what happened, but it just started working. Unfortunately, it seems to conflict with the code I'm using (might have to do with certain interrupts).
Glad to hear it's working with Eclipse. I would recommend using this library exclusively for all PWM in the project, even for pins that support hardware PWM. So you would just need to configure PWM channels for all pins you are using for PWM and replace all the calls to analogWrite()
with PalatisSoftPWM.set()
. That should solve the conflict issue.
Okay, I've started running into compile issues again when converting all PWM functions to use the library. Currently using the Arduino IDE and getting "PalatisSoftPWM.set not defined in scope" errors. The code has functions that are defined in a separate file called "Light.cpp" (PalatisSoftPWM.set is called in this file). Should I use extern?
Here are the accompanying Light.cpp and Light.h (respectively)
/*
* Light.cpp
*
* author: Sebastien CAPOU (neskweek@gmail.com)
* Source : https://github.com/neskweek/LightSaberOS
*/
#include "Light.h"
#include "Config.h"
// ====================================================================================
// === LED FUNCTIONS ===
// ====================================================================================
#ifdef LEDSTRINGS
static uint8_t flickerPos = 0;
static long lastFlicker = millis();
void lightOn(uint8_t ledPins[], int8_t segment) {
// Light On
if (segment == -1) {
for (uint8_t i = 0; i < 6; i++) {
digitalWrite(ledPins[i], HIGH);
}
} else {
digitalWrite(ledPins[segment], HIGH);
}
/* Should have save some hex size
* Needs further testing
switch (segment) {
case -1: // Light up everything at once !
PORTD |= B01101000;
PORTB |= B00001110;
break;
case 0:
PORTD |= (1 << PD3);//DPIN 3
break;
case 1:
PORTD |= (1 << PD5);//DPIN 5
break;
case 2:
PORTD |= (1 << PD6);//DPIN 6
break;
case 3:
PORTB |= (1 << PD1);//DPIN 9
break;
case 4:
PORTB |= (1 << PD2);//DPIN 10
break;
case 5:
PORTB |= (1 << PD3);//DPIN 11
break;
}
*/
} //lightOn
void lightOff(uint8_t ledPins[]) {
// shut Off
//Shut down PWM
TCCR0A &= ~((1 << COM0A1) | (1 << COM0B1));
TCCR1A &= ~((1 << COM1A1) | (1 << COM1B1));
TCCR2A &= ~((1 << COM2A1) | (1 << COM2B1));
//Shut down everything at once
PORTB &= B11110001;
PORTD &= B10010111;
} //lightOff
void lightIgnition(uint8_t ledPins[], uint16_t time, uint8_t type) {
switch (type) {
case 0:
// Light up the ledstrings Movie-like
for (uint8_t i = 0; i < 6; i++) {
digitalWrite(ledPins[i], HIGH);
delay(time / 5);
}
/*
PORTD |= (1 << PD3);//DPIN 3
delay(time / 5);
PORTD |= (1 << PD5);//DPIN 5
delay(time / 5);
PORTD |= (1 << PD6);//DPIN 6
delay(time / 5);
PORTB |= (1 << PD1);//DPIN 9
delay(time / 5);
PORTB |= (1 << PD2);//DPIN 10
delay(time / 5);
PORTB |= (1 << PD3);//DPIN 11
*/
break;
case 1:
for (int8_t i = 5; i >= 0; i--) {
for (uint8_t j = 0; j <= i; j++) {
if (j > 0) {
digitalWrite(ledPins[j - 1], LOW);
}
digitalWrite(ledPins[j], HIGH);
delay(time / 20);
}
}
/*
digitalWrite(ledPins[0], HIGH);
delay(time / 20);
digitalWrite(ledPins[0], LOW);
digitalWrite(ledPins[1], HIGH);
delay(time / 20);
digitalWrite(ledPins[1], LOW);
digitalWrite(ledPins[2], HIGH);
delay(time / 20);
digitalWrite(ledPins[2], LOW);
digitalWrite(ledPins[3], HIGH);
delay(time / 20);
digitalWrite(ledPins[3], LOW);
digitalWrite(ledPins[4], HIGH);
delay(time / 20);
digitalWrite(ledPins[4], LOW);
digitalWrite(ledPins[5], HIGH);
delay(time / 20);
digitalWrite(ledPins[0], HIGH);
delay(time / 20);
digitalWrite(ledPins[0], LOW);
digitalWrite(ledPins[1], HIGH);
delay(time / 20);
digitalWrite(ledPins[1], LOW);
digitalWrite(ledPins[2], HIGH);
delay(time / 20);
digitalWrite(ledPins[2], LOW);
digitalWrite(ledPins[3], HIGH);
delay(time / 20);
digitalWrite(ledPins[3], LOW);
digitalWrite(ledPins[4], HIGH);
delay(time / 20);
digitalWrite(ledPins[0], HIGH);
delay(time / 20);
digitalWrite(ledPins[0], LOW);
digitalWrite(ledPins[1], HIGH);
delay(time / 20);
digitalWrite(ledPins[1], LOW);
digitalWrite(ledPins[2], HIGH);
delay(time / 20);
digitalWrite(ledPins[2], LOW);
digitalWrite(ledPins[3], HIGH);
delay(time / 20);
digitalWrite(ledPins[0], HIGH);
delay(time / 20);
digitalWrite(ledPins[0], LOW);
digitalWrite(ledPins[1], HIGH);
delay(time / 20);
digitalWrite(ledPins[1], LOW);
digitalWrite(ledPins[2], HIGH);
delay(time / 20);
digitalWrite(ledPins[0], HIGH);
delay(time / 20);
digitalWrite(ledPins[0], LOW);
digitalWrite(ledPins[1], HIGH);
delay(time / 20);
digitalWrite(ledPins[0], HIGH);
// Light up the ledstrings invert
/*
PORTD |= (1 << PD3);
delay(time / 20);
PORTD &= ~(1 << PD3);
PORTD |= (1 << PD5);
delay(time / 20);
PORTD &= ~(1 << PD5);
PORTD |= (1 << PD6);
delay(time / 20);
PORTD &= ~(1 << PD6);
PORTB |= (1 << PD1);
delay(time / 20);
PORTB &= ~(1 << PD1);
PORTB |= (1 << PD2);
delay(time / 20);
PORTB &= ~(1 << PD1);
PORTB |= (1 << PD3);
delay(time / 20);
PORTD |= (1 << PD3);
delay(time / 20);
PORTD &= ~(1 << PD3);
PORTD |= (1 << PD5);
delay(time / 20);
PORTD &= ~(1 << PD5);
PORTD |= (1 << PD6);
delay(time / 20);
PORTD &= ~(1 << PD6);
PORTB |= (1 << PD1);
delay(time / 20);
PORTB &= ~(1 << PD1);
PORTB |= (1 << PD2);
delay(time / 20);
PORTD |= (1 << PD3);
delay(time / 20);
PORTD &= ~(1 << PD3);
PORTD |= (1 << PD5);
delay(time / 20);
PORTD &= ~(1 << PD5);
PORTD |= (1 << PD6);
delay(time / 20);
PORTD &= ~(1 << PD6);
PORTB |= (1 << PD1);
delay(time / 20);
PORTD |= (1 << PD3);
delay(time / 20);
PORTD &= ~(1 << PD3);
PORTD |= (1 << PD5);
delay(time / 20);
PORTD &= ~(1 << PD5);
PORTD |= (1 << PD6);
delay(time / 20);
PORTD |= (1 << PD3);
delay(time / 20);
PORTD &= ~(1 << PD3);
PORTD |= (1 << PD5);
delay(time / 20);
PORTD |= (1 << PD3);
*/
break;
}
} //lightIgnition
void lightRetract(uint8_t ledPins[], uint16_t time, uint8_t type) {
/*
//Make sure the light will be on after PWM shut off
PORTB |= B00001110;
PORTD |= B01101000;
//Shut down PWM
TCCR0A &= ~((1 << COM0A1) | (1 << COM0B1));
TCCR1A &= ~((1 << COM1A1) | (1 << COM1B1));
TCCR2A &= ~((1 << COM2A1) | (1 << COM2B1));
*/
switch (type) {
case 0:
// Light off the ledstrings Movie Like
for (int8_t i = 5; i >= 0; i--) {
digitalWrite(ledPins[i], LOW);
delay(time / 5);
}
/*
//Shut down each Digital PIN
PORTB &= ~(1 << PD3); //DPIN 11
delay(time / 5);
PORTB &= ~(1 << PD2); //DPIN 10
delay(time / 5);
PORTB &= ~(1 << PD1); //DPIN 9
delay(time / 5);
PORTD &= ~(1 << PD6); //DPIN 6
delay(time / 5);
PORTD &= ~(1 << PD5); //DPIN 5
delay(time / 5);
PORTD &= ~(1 << PD3); //DPIN 3
*/
break;
case 1:
// Light off the ledstrings invert
for (int8_t i = 5; i >= 0; i--) {
for (uint8_t j = 0; j <= i; j++) {
if (j > 0) {
digitalWrite(ledPins[j - 1], HIGH);
}
digitalWrite(ledPins[j], LOW);
delay(time / 20);
}
}
/*
PORTD &= ~(1 << PD3);
delay(time / 20);
PORTD &= ~(1 << PD5);
PORTD |= (1 << PD3);
delay(time / 20);
PORTD &= ~(1 << PD3);
delay(time / 20);
PORTD &= ~(1 << PD6);
PORTD |= (1 << PD5);
delay(time / 20);
PORTD &= ~(1 << PD5);
PORTD |= (1 << PD3);
delay(time / 20);
PORTD &= ~(1 << PD3);
delay(time / 20);
PORTB &= ~(1 << PD1);
PORTD |= (1 << PD6);
delay(time / 20);
PORTD &= ~(1 << PD6);
PORTD |= (1 << PD5);
delay(time / 20);
PORTD &= ~(1 << PD5);
PORTD |= (1 << PD3);
delay(time / 20);
PORTD &= ~(1 << PD3);
delay(time / 20);
PORTB &= ~(1 << PD2);
PORTB |= (1 << PD1);
delay(time / 20);
PORTB &= ~(1 << PD1);
PORTD |= (1 << PD6);
delay(time / 20);
PORTD &= ~(1 << PD6);
PORTD |= (1 << PD5);
delay(time / 20);
PORTD &= ~(1 << PD5);
PORTD |= (1 << PD3);
delay(time / 20);
PORTD &= ~(1 << PD3);
delay(time / 20);
PORTB &= ~(1 << PD3);
PORTB |= (1 << PD2);
delay(time / 20);
PORTB &= ~(1 << PD2);
PORTB |= (1 << PD1);
delay(time / 20);
PORTB &= ~(1 << PD1);
PORTD |= (1 << PD6);
delay(time / 20);
PORTD &= ~(1 << PD6);
PORTD |= (1 << PD5);
delay(time / 20);
PORTD &= ~(1 << PD5);
PORTD |= (1 << PD3);
delay(time / 20);
PORTD &= ~(1 << PD3);
*/
break;
}
} //lightRetract
#ifdef FoCSTRING
void FoCOn(uint8_t pin) {
digitalWrite(FoCSTRING, LOW);
// PORTC &= ~(1 << PD3);
} //FoCOn
void FoCOff(uint8_t pin) {
digitalWrite(FoCSTRING, HIGH);
// PORTC |= (1 << PD3);
} //FoCOff
#endif
void lightFlicker(uint8_t ledPins[], uint8_t type, uint8_t value) {
uint8_t variation = abs(analogRead(SPK1) - analogRead(SPK2));
uint8_t brightness;
if (not value) {
// Calculation of the amount of brightness to fade
brightness = MAX_BRIGHTNESS - variation;
} else {
brightness = value;
}
#ifdef LS_HEAVY_DEBUG
Serial.print(F("Brightness: "));
Serial.print(brightness);
Serial.print(F(" SPK1: "));
Serial.print(analogRead(SPK1));
Serial.print(F(" SPK2: "));
Serial.println(analogRead(SPK2));
#endif
switch (type) {
case 0:
// std Flickering
for (uint8_t i = 0; i <= 7; i++) {
analogWrite(i, brightness);
}
break;
case 1:
// pulse Flickering
for (uint8_t i = 0; i <= 7; i++) {
if (i != flickerPos)
PalatisSoftPWM.set(i, brightness - variation / 2);
else
PalatisSoftPWM.set(i, MAX_BRIGHTNESS);
}
if ((flickerPos != 0
and millis() - lastFlicker > (120 - (100 - 15 * flickerPos)))
or (flickerPos == 0 and millis() - lastFlicker > 300)) {
flickerPos++;
lastFlicker = millis();
if (flickerPos == 6) {
flickerPos = 0;
}
}
break;
case 2:
// anarchic Flickering
for (uint8_t i = 0; i <= 5; i++) {
randomSeed(analogRead(ledPins[i]));
PalatisSoftPWM.set(i,MAX_BRIGHTNESS - random(variation, variation * 2));
}
break;
}
} //lightFlicker
#endif
#ifdef LUXEON
void lightOn(uint8_t ledPins[], uint8_t color[]) {
// Light On
for (uint8_t i = 0; i <= 2; i++) {
analogWrite(ledPins[i], MAX_BRIGHTNESS * color[i] / rgbFactor);
}
} //lightOn
void lightOff(uint8_t ledPins[]) {
// shut Off
for (uint8_t i = 0; i <= 2; i++) {
analogWrite(ledPins[i], LOW);
}
} //lightOff
void lightIgnition(uint8_t ledPins[], uint8_t color[], uint16_t time) {
// Fade in to Maximum brightness
for (uint8_t fadeIn = 255; fadeIn > 0; fadeIn--) {
for (uint8_t i = 0; i <= 2; i++) {
analogWrite(ledPins[i],
(MAX_BRIGHTNESS / fadeIn) * color[i] / rgbFactor);
}
delay(time / 255);
}
} //lightIgnition
void lightRetract(uint8_t ledPins[], uint8_t color[], uint16_t time) {
// Fade out
for (uint8_t fadeOut = 1; fadeOut < 255; fadeOut++) {
for (uint8_t i = 0; i <= 2; i++) {
analogWrite(ledPins[i],
(MAX_BRIGHTNESS / fadeOut) * color[i] / rgbFactor);
}
delay(time / 255);
}
lightOff(ledPins);
} //lightRetract
void lightFlicker(uint8_t ledPins[], uint8_t color[], uint8_t value) {
uint8_t brightness;
if (not value) {
// Calculation of the amount of brightness to fade
brightness = MAX_BRIGHTNESS
- (abs(analogRead(SPK1) - analogRead(SPK2)));
} else {
brightness = value;
}
#ifdef LS_HEAVY_DEBUG
Serial.print(F("Brightness: "));
Serial.print(brightness);
Serial.print(F(" SPK1: "));
Serial.print(analogRead(SPK1));
Serial.print(F(" SPK2: "));
Serial.println(analogRead(SPK2));
#endif
for (uint8_t i = 0; i <= 2; i++) {
analogWrite(ledPins[i], brightness * color[i] / rgbFactor);
}
} //lightFlicker
#ifdef MY_OWN_COLORS
void getColor(uint8_t color[], uint8_t colorID) {
color[3] = colorID;
switch (colorID) {
case 0:
//Red
color[0] = 100;
color[1] = 0;
color[2] = 0;
break;
case 1:
//Green
color[0] = 0;
color[1] = 100;
color[2] = 0;
break;
case 2:
//Blue
color[0] = 0;
color[1] = 0;
color[2] = 100;
break;
default:
// White?
color[0] = 100;
color[1] = 100;
color[2] = 100;
break;
}
} //getColor
#else
void getColor(uint8_t color[], uint16_t colorID) {
uint8_t tint = (COLORS / 6);
uint8_t step = rgbFactor / tint;
color[3] = colorID;
if ((colorID >= 0) and (colorID < (1 * tint))) {
//From Red to Yellow
color[0] = 100;
color[1] = step * (colorID % tint);
color[2] = 0;
} else if ((colorID >= (1 * tint)) and (colorID < (2 * tint))) {
// From Yellow to Green
color[0] = 100 - (step * (colorID % tint));
color[1] = 100;
color[2] = 0;
} else if ((colorID >= (2 * tint)) and (colorID < (3 * tint))) {
// From Green to Aqua
color[0] = 0;
color[1] = 100;
color[2] = step * (colorID % tint);
} else if ((colorID >= (3 * tint)) and (colorID < (4 * tint))) {
// From Aqua to Blue
color[0] = 0;
color[1] = 100 - (step * (colorID % tint));
color[2] = 100;
} else if ((colorID >= (4 * tint)) and (colorID < (5 * tint))) {
// From Blue to Purple/Pink
color[0] = step * (colorID % tint);
color[1] = 0;
color[2] = 100;
} else if (colorID >= (5 * tint)) {
// From Purple/Pink to Red
color[0] = 100;
color[1] = 0;
color[2] = 100 - (step * (colorID % tint));
}
} //getColor
#endif
#endif
and Light.h
/*
* Light.h
*
* Created on: 6 mars 2016
* author: Sebastien CAPOU (neskweek@gmail.com)
* Source : https://github.com/neskweek/LightSaberOS
*/
#ifndef LIGHT_H_
#define LIGHT_H_
#include <Arduino.h>
#include <PalatisSoftPWM.h>
// ====================================================================================
// === LED FUNCTIONS ===
// ====================================================================================
#ifdef LEDSTRINGS
void lightOn(uint8_t ledPins[], int8_t segment = -1);
void lightOff(uint8_t ledPins[]);
void lightIgnition(uint8_t ledPins[], uint16_t time, uint8_t type);
void lightRetract(uint8_t ledPins[], uint16_t time, uint8_t type);
void FoCOn (uint8_t pin);
void FoCOff (uint8_t pin);
void lightFlicker(uint8_t ledPins[], uint8_t type, uint8_t value = 0);
#endif
#ifdef LUXEON
void lightOn(uint8_t ledPins[], uint8_t color[]);
void lightOff(uint8_t ledPins[]);
void lightIgnition(uint8_t ledPins[], uint8_t color[], uint16_t time);
void lightRetract(uint8_t ledPins[], uint8_t color[], uint16_t time);
void lightFlicker(uint8_t ledPins[], uint8_t color[], uint8_t value = 0);
#ifdef MY_OWN_COLORS
void getColor(uint8_t color[], uint8_t colorID); //getColor
#endif
#ifdef FIXED_RANGE_COLORS
void getColor(uint8_t color[], uint16_t colorID); //getColor
#endif
#endif
#endif /* LIGHT_H_ */
Add to Light.h after the line #include <PalatisSoftPWM.h>
:
SOFTPWM_DEFINE_EXTERN_OBJECT(n);
where n is the number of PWM channels you have defined in Lightsaber.ino
Leave the SOFTPWM_DEFINE_OBJECT(n);
in Lightsaber.ino. I can't test the Lightsaber code because I don't have the hardware but this worked in my simplified test sketch and it makes the LightSabreOS code compile with the code you have posted here substituted in. So let me know how it goes for you. I haven't used the EXTERN_OBJECT feature of this library before so I learned something. I'm going to make an example that demonstrates how to use it.
That worked! Unfortunately, I think the nature of how this library works and the use of interrupts in the rest of the code is making my lightsaber unresponsive. Do you think there'd be any possible conflicts with I2Cdev, Wire, or MPU6050 libraries?
Well to be honest that question may be a bit beyond my level of knowledge. I am not actually the author of this library, I just forked it and made some changes to attempt to make it more user friendly. PalatisSoftPWM uses Timer1 and I've had issues trying to use this library and analogWrite()
at the same time, I assume because I was using analogWrite on pins that use Timer1 for hardware PWM. It may be that you can still use the hardware PWM on the pins that don't use Timer1. The problem with software PWM vs hardware PWM is it is very processor and interrupt intensive. The extent of this depends on how many soft PWM channels you have defined, how many PWM levels, and the PWM frequency. If the interrupt load is too high then that could interfere with the rest of your code running. You can check this by the use of PalatisSoftPWM.printInterruptLoad()
, which will print various diagnostic information to the Serial Monitor(see the readme for explanation of the output). So you could try setting the number of levels(with SOFTPWM_DEFINE_OBJECT_WITH_PWM_LEVELS()
) and the frequency(with PalatisSoftPWM.begin()
) to very low values to see if that's the cause of the problem and then see if you can increase them to reasonable numbers without causing loop starvation. If that doesn't work you could write simplified test programs using soft PWM in conjunction with other individual components of the light saber to see if you can narrow down what is conflicting with this library..
Since there doesn't appear to have been any issues with the library I'm going to close this issue but if you make any progress on using this library with LSOS please comment.
The PWM()
function recently added to LSOS may be more simple but also much less efficient than PalatisSoftPWM due to its use of digitalWrite()
.
If you find any bugs with this library please open a new issue.
The following errors are thrown when attempting to compile w/ Arduino Eclipse
Issue appears to be with the PalatisSoftPWM.begin(60); statement in setup(). Upon looking inside PalatisSoftPWM.h, the class name is CSoftPWM. When changed to CSoftPWM.begin(60); only the following errors are thrown: