/*---------------------------------------------------------------------------------------
Rubik Lamp V2
Author: John Bradnam
BOARD: ATtiny1614/1604/814/804/414/404/214/204
Chip: ATtiny1614
Clock Speed: 20MHz (Required!!)
Programmer: jtag2updi (megaTinyCore)
ATTiny1614 Pins mapped to Ardunio Pins
2020/08/15 V1 - Initial Build
2021/01/04 V2 - Added LAMP_RUBIK_ANIMATED mode
2021/01/05 V3 - Fixed bug in Bluetooth mode
*/
#include <tinyNeoPixel_Static.h>
#include <avr/sleep.h>
#ifdef __AVR_ATtiny1614__
#include <avr/eeprom.h>
#else
#include <EEPROM.h>
#endif
/*
+--------+
VCC + 1 14 + GND
(SS) 0 PA4 + 2 13 + PA3 10 (SCK)
1 PA5 + 3 12 + PA2 9 (MISO)
(DAC) 2 PA6 + 4 11 + PA1 8 (MOSI)
3 PA7 + 5 10 + PA0 11 (UPDI)
(RXD) 4 PB3 + 6 9 + PB0 7 (SCL)
(TXD) 5 PB2 + 7 8 + PB1 6 (SDA)
+--------+
PA0 to PA7 can be analog or digital
PWM on D0, D1, D6, D7, D10
*/
#define LED 0 //PA4
#define MODE 1 //PA5
#define RANDOM 3 //PA7
#define RX 4 //PB3
#define TX 5 //PB2
/*
* --- Logical LED positions (LED 04 is not present => logical 05 is physical 04 ---
top
* +----+----+----+
* | 53 | 52 | 51 |
* +----+----+----+
* | 50 | 49 | 48 |
* +----+----+----+
* | 47 | 46 | 45 |
* +----+-fr-+----+
left front right back
* +----+----+----+ +----+----+----+ +----+----+----+ +----+----+----+
* | 26 | 25 | 24 | | 17 | 16 | 15 | | 44 | 43 | 42 | | 33 | 34 | 35 |
* +----+----+----+ +----+----+----+ +----+----+----+ +----+----+----+
* | 23 | 22 | 21 f | 14 | 13 | 12 | f 41 | 40 | 39 | | 30 | 31 | 32 | (looking from front)
* +----+----+----+ +----+----+----+ +----+----+----+ +----+----+----+
* | 20 | 19 | 18 | | 11 | 10 | 09 | | 38 | 37 | 36 | | 27 | 28 | 29 |
* +----+----+----+ +----+----+----+ +----+----+----+ +----+----+----+
bottom
* +----+----+----+
* | 02 | 01 | 00 |
* +----+----+----+
* | 05 | 04 | 03 | (looking from top)
* +----+----+----+
* | 08 | 07 | 06 |
* +----+-fr-+----+
*/
// Face rotations
const uint8_t u1[] PROGMEM = { 53, 52, 51, 48, 45, 46, 47, 50, 53 };
const uint8_t u2[] PROGMEM = { 15, 16, 17, 24, 25, 26, 33, 34, 35, 42, 43, 44, 15 };
const uint8_t d1[] PROGMEM = { 2, 1, 0, 3, 6, 7, 8, 5, 2 };
const uint8_t d2[] PROGMEM = { 11, 10, 9, 38, 37, 36, 29, 28, 27, 20, 19, 18, 11 };
const uint8_t f1[] PROGMEM = { 17, 16, 15, 12, 9, 10, 11, 14, 17 };
const uint8_t f2[] PROGMEM = { 47, 46, 45, 44, 41, 38, 6, 7, 8, 18, 21, 24, 47 };
const uint8_t b1[] PROGMEM = { 33, 34, 35, 32, 29, 28, 27, 30, 33 };
const uint8_t b2[] PROGMEM = { 53, 52, 51, 42, 39, 36, 0, 1, 2, 20, 23, 26, 53 };
const uint8_t l1[] PROGMEM = { 26, 25, 24, 21, 18, 19, 20, 23, 26 };
const uint8_t l2[] PROGMEM = { 17, 14, 11, 8, 5, 2, 27, 30, 33, 53, 50, 47, 17 };
const uint8_t r1[] PROGMEM = { 42, 43, 44, 41, 38, 37, 36, 39, 42 };
const uint8_t r2[] PROGMEM = { 15, 12, 9, 6, 3, 0, 29, 32, 35, 51, 48, 45, 15 };
//Slice rotations
const uint8_t m2[] PROGMEM = { 16, 13, 10, 7, 4, 1, 28, 31, 34, 52, 49, 46, 16 };
const uint8_t e2[] PROGMEM = { 14, 13, 12, 41, 40, 39, 32, 31, 30, 23, 22, 21, 14 };
const uint8_t s2[] PROGMEM = { 50, 49, 48, 43, 40, 37, 3, 4, 5, 19, 22, 25, 50 };
//Push Switch
#define NEXT_BUTTON_TIMEOUT 3000 //Maximum time between button presses before button becomes a sleep button.
#define BRIGHTNESS_INITIAL_TIMOUT 500 //Time button needs to be held down before it is treated as a long press
#define BRIGHTNESS_TIMOUT 50 //Speed at which brightness changes on a long press
#define ROTATE_SPEED 200 //Speed at which cube pieces rotate
uint32_t debounceTimeout = 0;
uint32_t buttonNextTimeout = 0; //Used to timeout mode switch function to sleep function
bool ignoreNextButton = false; //Used to block button action when waking up
uint32_t brightnessTimeout = 0; //Timeout used to determine when mode switch is held down
bool first = true; //Used to initialise new mode
//NeoPixel library
#define NUMPIXELS 53
uint32_t cube[NUMPIXELS + 1];
byte pixels[NUMPIXELS * 3];
tinyNeoPixel strip = tinyNeoPixel(NUMPIXELS, LED, NEO_GRB, pixels);
uint8_t nextRainbowSide = 0;
uint8_t nextRainbowCube = 0;
uint8_t nextAnimatedSequence = 0;
uint8_t animatedCubeMoves = 0;
uint32_t rainbowTimeout = 0;
uint32_t animtedCubeTimeout = 0;
#define RAINBOW_SIDE_DELAY 10
#define RAINBOW_CUBE_DELAY 20
#define ANIMATED_CUBE_DELAY 500
#define ANIMATED_SOLVED_DELAY 3000
#define ANIMATED_CUBE_MIN_MOVES 18
#define ANIMATED_CUBE_MAX_MOVES 24
uint8_t cubeSteps[ANIMATED_CUBE_MAX_MOVES];
//Modes
enum MODES { LAMP_WHITE, LAMP_RED, LAMP_GREEN, LAMP_BLUE, LAMP_RAINBOW_SIDE, LAMP_RAINBOW_CUBE, LAMP_RUBIK_ANIMATED, LAMP_RUBIK, END_OF_MODES };
//EEPROM handling
#define EEPROM_ADDRESS 0
#define EEPROM_MAGIC 0x0BAD0DAD
typedef struct {
uint32_t magic;
MODES mode; //Current mode
uint16_t brightness; //Current brightness level
} EEPROM_DATA;
EEPROM_DATA EepromData; //Current EEPROM settings
#define BLUETOOTH_BUFFER_SIZE 64
char btBuffer[BLUETOOTH_BUFFER_SIZE + 1];
// PA5 pin button interrupt (MODE switch)
void buttonInterrupt()
{
}
//Shut down ATtiny1614
//Will wake up when button is pressed
void enterSleep()
{
strip.clear();
strip.show();
//cbi(ADCSRA,ADEN); // switch Analog to Digitalconverter OFF
set_sleep_mode(SLEEP_MODE_PWR_DOWN); // sleep mode is set here
sleep_enable();
sleep_mode(); // System actually sleeps here
sleep_disable(); // System continues execution here when watchdog timed out
//sbi(ADCSRA,ADEN); // switch Analog to Digitalconverter ON
buttonNextTimeout = millis() + NEXT_BUTTON_TIMEOUT; //Time mode button can remain active before it reverts back to a sleep button
ignoreNextButton = true;
first = true; //Repaint the screen
}
//-----------------------------------------------------------------------
//Setup lamp
void setup()
{
//Eprom
readEepromData();
randomSeed(analogRead(RANDOM));
//Bluetooth setup
Serial.begin(9600);
Serial.println("Hello from Rubik Lamp V1");
pinMode(MODE,INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(MODE),buttonInterrupt,CHANGE);
pinMode(LED,OUTPUT);
strip.setBrightness(EepromData.brightness);
strip.show(); // Initialize all pixels to 'off'
//Start in sleep mode
enterSleep();
}
//-----------------------------------------------------------------------
//Main loop
// Handle button press. If pressed within NEXT_BUTTON_TIMEOUT, advance to
// next animation otherwise turn off the lamp
void loop()
{
MODES m = EepromData.mode;
testButtonPress();
testBluetoothInput();
//If mode has changed, store it in EEPROM and update display
if (first)
{
writeEepromData();
buttonNextTimeout = millis() + NEXT_BUTTON_TIMEOUT;
//Static modes
switch (EepromData.mode)
{
case LAMP_WHITE: colorWash(strip.Color(255, 255, 255)); break;
case LAMP_RED: colorWash(strip.Color(255, 0, 0)); break;
case LAMP_GREEN: colorWash(strip.Color(0, 255, 0)); break;
case LAMP_BLUE: colorWash(strip.Color(0, 0, 255)); break;
case LAMP_RUBIK: colorRubik(); break;
}
}
//Dynamic modes
switch (EepromData.mode)
{
case LAMP_RAINBOW_SIDE: colorRainbowSide(first); break;
case LAMP_RAINBOW_CUBE: colorRainbowCube(first); break;
case LAMP_RUBIK_ANIMATED: colorRubikAnimated(first); break;
}
first = false;
delay(100);
}
//-----------------------------------------------------------------------
// Handle bluetooth commands
// Obtain characters from HC06 and process request
void testBluetoothInput()
{
int8_t oldMode = EepromData.mode;
//Fill buffer with multiple commands
int pos = 0;
char a;
while (Serial.available())
{
a = Serial.read();
if (pos < BLUETOOTH_BUFFER_SIZE)
{
btBuffer[pos] = a;
pos++;
}
}
btBuffer[pos] = '\0';
//Process each command in turn
pos = 0;
a = btBuffer[pos++];
while (a != '\0')
{
char face = (a & ~0x20);
bool lc = (a >= 'a');
if (EepromData.mode == LAMP_RUBIK)
{
switch (face)
{
case 'F': rotateFace(f1, f2, lc); break;
case 'B': rotateFace(b1, b2, lc); break;
case 'U': rotateFace(u1, u2, lc); break;
case 'D': rotateFace(d1, d2, lc); break;
case 'L': rotateFace(l1, l2, !lc); break;
case 'R': rotateFace(r1, r2, !lc); break;
case 'M': rotateFace(NULL, m2, lc); break;
case 'S': rotateFace(NULL, s2, lc); break;
case 'E': rotateFace(NULL, e2, lc); break;
case 'X': rotateCubeOnR(lc); break;
case 'Y': rotateCubeOnU(lc); break;
case 'Z': rotateCubeOnF(lc); break;
case 'W': EepromData.mode = LAMP_WHITE; break; //Switch to WHITE light
}
}
else
{
switch (face)
{
case 'C': EepromData.mode = LAMP_RUBIK; break; //Switch to CUBE mode
case 'R': EepromData.mode = LAMP_RED; break; //Switch to RED light
case 'G': EepromData.mode = LAMP_GREEN; break; //Switch to GREEN light
case 'B': EepromData.mode = LAMP_BLUE; break; //Switch to BLUE light
case 'W': EepromData.mode = LAMP_WHITE; break; //Switch to WHITE light
case 'J': EepromData.mode = LAMP_RAINBOW_SIDE; break; //Switch to RAINBOW SIDE light
case 'K': EepromData.mode = LAMP_RAINBOW_CUBE; break; //Switch to RAINBOW CUBE light
}
}
a = btBuffer[pos++];
}
first = first | (oldMode != EepromData.mode);
}
//-----------------------------------------------------------------------
// Handle button processing
// If last button press was more than NEXT_BUTTON_TIMEOUT, go to sleep
// otherwise switch to next mode
void testButtonPress()
{
//Test button
if (isButtonPressed())
{
if (ignoreNextButton)
{
ignoreNextButton = false; //Ignore if coming out of a sleep
}
else if (millis() > buttonNextTimeout)
{
enterSleep();
}
else
{
EepromData.mode = (MODES)((int)EepromData.mode + 1);
if (EepromData.mode == END_OF_MODES)
{
EepromData.mode = LAMP_WHITE;
}
first = true;
}
}
}
//-----------------------------------------------------------------------
//Test if button is pressed
// Not pressed returns FALSE
// Short press (debounced) returns TRUE
// Long press adjust brightness level
bool isButtonPressed()
{
bool pressed = false;
bool brightnessChanged = false;
if (digitalRead(MODE) == LOW)
{
//start timing switch
debounceTimeout = millis() + 10;
while (digitalRead(MODE) == LOW && millis() < debounceTimeout)
{
yield();
}
if (digitalRead(MODE) == LOW)
{
pressed = true;
//button is being pressed
brightnessTimeout = millis() + BRIGHTNESS_INITIAL_TIMOUT;
while (digitalRead(MODE) == LOW)
{
if (millis() > brightnessTimeout)
{
EepromData.brightness = max((EepromData.brightness + 2) & 0xFF, 0x04);
strip.setBrightness(EepromData.brightness);
strip.show();
brightnessTimeout = millis() + BRIGHTNESS_TIMOUT;
buttonNextTimeout = millis() + NEXT_BUTTON_TIMEOUT; //Reset sleep button timeout
pressed = false;
brightnessChanged = true;
}
yield();
}
if (brightnessChanged)
{
writeEepromData();
}
}
}
return pressed;
}
//-----------------------------------------------------------------------
//Display a single color
// c - color to wash cube with
void colorWash(uint32_t c)
{
for(uint16_t i = 0; i < strip.numPixels(); i++)
{
strip.setPixelColor(i, c);
}
strip.show();
}
//-----------------------------------------------------------------------
//Display a rainbow of colors
void colorRainbowSide(bool first)
{
if (first || millis() > rainbowTimeout)
{
rainbowTimeout = millis() + RAINBOW_SIDE_DELAY;
uint32_t color;
int j = 0;
for (int i=0; i < strip.numPixels(); i++)
{
if (i < 8)
{
//Bottom
color = Wheel((nextRainbowSide + 0) & 0xFF);
}
else if (i < 17)
{
//Front
color = Wheel((nextRainbowSide + 42) & 0xFF);
}
else if (i < 26)
{
//Left
color = Wheel((nextRainbowSide + 84) & 0xFF);
}
else if (i < 35)
{
//Back
color = Wheel((nextRainbowSide + 126) & 0xFF);
}
else if (i < 44)
{
//Right
color = Wheel((nextRainbowSide + 168) & 0xFF);
}
else
{
//Top
color = Wheel((nextRainbowSide + 210) & 0xFF);
}
cube[j++] = color;
if (j == 4)
{
//Set missing LED spot to same color as last pixel
cube[j++] = color;
}
}
cubePaint();
nextRainbowSide = (nextRainbowSide + 1) & 0xFF;
}
}
//-----------------------------------------------------------------------
//Display a rainbow of colors
void colorRainbowCube(bool first)
{
if (first || millis() > rainbowTimeout)
{
rainbowTimeout = millis() + RAINBOW_CUBE_DELAY;
uint32_t color = Wheel(nextRainbowCube & 0xFF);
int j = 0;
for (int i=0; i < strip.numPixels(); i++)
{
cube[j++] = color;
if (j == 4)
{
//Set missing LED spot to same color as last pixel
cube[j++] = color;
}
}
cubePaint();
nextRainbowCube = (nextRainbowCube + 1) & 0xFF;
}
}
//-----------------------------------------------------------------------
//Show Rubik cube, randomise the cube, solve the cube
void colorRubikAnimated(bool initialise)
{
uint8_t nextMove;
uint8_t lastMove;
if (initialise || (millis() > animtedCubeTimeout && nextAnimatedSequence == 0))
{
//Paint cube
colorRubik();
//Randomize cube
lastMove = 0;
nextMove = 1;
nextAnimatedSequence = 0;
animatedCubeMoves = random(ANIMATED_CUBE_MIN_MOVES, ANIMATED_CUBE_MAX_MOVES + 1);
while (nextAnimatedSequence < animatedCubeMoves && !first)
{
//Get next move that isn't the reverse of the last move
nextMove = random(0,18);
while ((nextMove >> 1) == (lastMove >> 1) && (nextMove & 0x01 != lastMove & 0x01))
{
nextMove = random(0,18);
}
cubeSteps[nextAnimatedSequence] = nextMove;
moveSegment(nextMove);
nextAnimatedSequence++;
//Becuase this takes time, we need to check button test
testButtonPress();
}
//Start solving the cube
animtedCubeTimeout = millis() + ANIMATED_CUBE_DELAY;
}
if (millis() > animtedCubeTimeout)
{
nextAnimatedSequence--;
nextMove = cubeSteps[nextAnimatedSequence] ^ 0x01; //Reverse direction
moveSegment(nextMove);
animtedCubeTimeout = millis() + ((nextAnimatedSequence == 0) ? ANIMATED_SOLVED_DELAY : ANIMATED_CUBE_DELAY);
}
}
//-----------------------------------------------------------------------
//Moves the cube segment
// s = move - (bit 0 is direction, bits 4,3,2,1 is move type)
void moveSegment(uint8_t s)
{
bool lc = (s & 0x01);
switch (s >> 1)
{
case 0: rotateFace(f1, f2, lc); break;
case 1: rotateFace(b1, b2, lc); break;
case 2: rotateFace(u1, u2, lc); break;
case 3: rotateFace(d1, d2, lc); break;
case 4: rotateFace(l1, l2, !lc); break;
case 5: rotateFace(r1, r2, !lc); break;
case 6: rotateFace(NULL, m2, lc); break;
case 7: rotateFace(NULL, s2, lc); break;
case 8: rotateFace(NULL, e2, lc); break;
}
cubePaint();
}
//-----------------------------------------------------------------------
//Display colors of a rubik cube
void colorRubik()
{
uint32_t color;
int j = 0;
for (int i=0; i < strip.numPixels(); i++)
{
if (i < 8)
{
//Bottom
color = strip.Color(255, 64, 0); //Orange
}
else if (i < 17)
{
//Front
color = strip.Color(255, 255, 255); //White
}
else if (i < 26)
{
//Left
color = strip.Color(0, 0, 255); //Blue
}
else if (i < 35)
{
//Back
color = strip.Color(255, 255, 0); //Yellow
}
else if (i < 44)
{
//Right
color = strip.Color(0, 255, 0); //Green
}
else
{
//Top
color = strip.Color(255, 0, 0); //Red
}
cube[j++] = color;
if (j == 4)
{
//Set missing LED spot to same color as last pixel
cube[j++] = color;
}
}
cubePaint();
}
//---------------------------------------------------------------
//Transfer our cube buffer to the LED strip
void cubePaint()
{
for (int i=0, j=0; i < strip.numPixels(); i++)
{
strip.setPixelColor(i, cube[j++]);
//Skip over missing physical LED
if (j == 4)
{
j++;
}
}
strip.show();
}
//---------------------------------------------------------------
void rotatePixels(uint8_t* p, bool forward)
{
uint8_t first = pgm_read_byte_near(p++);
uint32_t colorFirst = cube[first];
uint8_t last = first;
uint8_t next;
if (forward)
{
next = pgm_read_byte_near(p++);
while (next != first)
{
cube[last] = cube[next];
last = next;
next = pgm_read_byte_near(p++);
}
}
else
{
while (pgm_read_byte_near(p) != first)
{
p++;
}
next = pgm_read_byte_near(--p);
while (next != first)
{
cube[last] = cube[next];
last = next;
next = pgm_read_byte_near(--p);
}
}
cube[last] = colorFirst;
}
//---------------------------------------------------------------
//Rotate the face of a cube
//
void rotateFace(uint8_t* p1, uint8_t* p2, bool forward)
{
for (int i = 0; i < 3; i++)
{
if (i != 1 && p1 != NULL)
{
rotatePixels(p1, forward);
}
if (p2 != NULL)
{
rotatePixels(p2, forward);
}
cubePaint();
delay(ROTATE_SPEED);
}
}
//---------------------------------------------------------------
//Rotate whole cube around the right hand center (X axis)
void rotateCubeOnR(bool lc)
{
for (int i = 0; i < 3; i++)
{
if (i != 1)
{
rotatePixels(l1, !lc);
rotatePixels(r1, !lc);
}
rotatePixels(l2, !lc);
rotatePixels(m2, !lc);
rotatePixels(r2, !lc);
cubePaint();
delay(ROTATE_SPEED);
}
}
//---------------------------------------------------------------
//Rotate whole cube around the up center (Y axis)
void rotateCubeOnU(bool lc)
{
for (int i = 0; i < 3; i++)
{
if (i != 1)
{
rotatePixels(u1, !lc);
rotatePixels(d1, lc);
}
rotatePixels(u2, !lc);
rotatePixels(e2, lc);
rotatePixels(d2, lc);
cubePaint();
delay(ROTATE_SPEED);
}
}
//---------------------------------------------------------------
//Rotate whole cube around the up front (Z axis)
void rotateCubeOnF(bool lc)
{
for (int i = 0; i < 3; i++)
{
if (i != 1)
{
rotatePixels(f1, lc);
rotatePixels(b1, lc);
}
rotatePixels(f2, lc);
rotatePixels(s2, lc);
rotatePixels(b2, lc);
cubePaint();
delay(ROTATE_SPEED);
}
}
//---------------------------------------------------------------
//Write the EepromData structure to EEPROM
void writeEepromData()
{
//This function uses EEPROM.update() to perform the write, so does not rewrites the value if it didn't change.
#ifdef __AVR_ATtiny1614__
eeprom_update_block (( void *) &EepromData , ( const void *) EEPROM_ADDRESS, sizeof(EepromData));
#else
EEPROM.put(EEPROM_ADDRESS,EepromData);
#endif
}
//---------------------------------------------------------------
//Read the EepromData structure from EEPROM, initialise if necessary
void readEepromData()
{
//Eprom
#ifdef __AVR_ATtiny1614__
eeprom_read_block (( void *) &EepromData , ( const void *) EEPROM_ADDRESS, sizeof(EepromData));
#else
EEPROM.get(EEPROM_ADDRESS,EepromData);
#endif
if (EepromData.magic != EEPROM_MAGIC)
{
EepromData.magic = EEPROM_MAGIC;
EepromData.mode = LAMP_WHITE;
EepromData.brightness = 0x40;
writeEepromData();
}
}
//-----------------------------------------------------------------------
void rainbow(uint8_t wait)
{
uint16_t i, j;
for(j=0; j<256; j++)
{
for(i=0; i<strip.numPixels(); i++)
{
strip.setPixelColor(i, Wheel((i+j) & 255));
}
strip.show();
delay(wait);
}
}
// Slightly different, this makes the rainbow equally distributed throughout
void rainbowCycle(uint8_t wait)
{
uint16_t i, j;
for(j=0; j<256*5; j++)
{ // 5 cycles of all colors on wheel
for(i=0; i< strip.numPixels(); i++)
{
strip.setPixelColor(i, Wheel(((i * 256 / strip.numPixels()) + j) & 255));
}
strip.show();
delay(wait);
}
}
//Theatre-style crawling lights.
void theaterChase(uint32_t c, uint8_t wait)
{
for (int j=0; j<10; j++)
{ //do 10 cycles of chasing
for (int q=0; q < 3; q++)
{
for (int i=0; i < strip.numPixels(); i=i+3)
{
strip.setPixelColor(i+q, c); //turn every third pixel on
}
strip.show();
delay(wait);
for (int i=0; i < strip.numPixels(); i=i+3)
{
strip.setPixelColor(i+q, 0); //turn every third pixel off
}
}
}
}
//Theatre-style crawling lights with rainbow effect
void theaterChaseRainbow(uint8_t wait)
{
for (int j=0; j < 256; j++)
{ // cycle all 256 colors in the wheel
for (int q=0; q < 3; q++)
{
for (int i=0; i < strip.numPixels(); i=i+3)
{
strip.setPixelColor(i+q, Wheel( (i+j) % 255)); //turn every third pixel on
}
strip.show();
delay(wait);
for (int i=0; i < strip.numPixels(); i=i+3)
{
strip.setPixelColor(i+q, 0); //turn every third pixel off
}
}
}
}
// Input a value 0 to 255 to get a color value.
// The colours are a transition r - g - b - back to r.
uint32_t Wheel(byte WheelPos)
{
WheelPos = 255 - WheelPos;
if(WheelPos < 85)
{
return strip.Color(255 - WheelPos * 3, 0, WheelPos * 3);
}
if(WheelPos < 170)
{
WheelPos -= 85;
return strip.Color(0, WheelPos * 3, 255 - WheelPos * 3);
}
WheelPos -= 170;
return strip.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
}
Comments
Please log in or sign up to comment.