-
Notifications
You must be signed in to change notification settings - Fork 776
/
Copy pathTechno_Tiki_With_Remote_Control.ino
276 lines (254 loc) · 11.9 KB
/
Techno_Tiki_With_Remote_Control.ino
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
// SPDX-FileCopyrightText: 2018 Tony DiCola for Adafruit Industries
// SPDX-FileCopyrightText: 2020 Erin St. Blaine for Adafruit Industries
//
// SPDX-License-Identifier: MIT
// Techno-Tiki RGB LED Torch with IR Remote Control
// Created by Tony DiCola
//
// See guide at: https://learn.adafruit.com/techno-tiki-rgb-led-torch/overview
//
// Released under a MIT license: http://opensource.org/licenses/MIT
#include <avr/power.h>
#include <avr/sleep.h>
#include <Adafruit_NeoPixel.h>
// Sketch configuration:
#define PIXEL_PIN 1 // Pin connected to the NeoPixel strip.
#define PIXEL_COUNT 6 // Number of NeoPixels. At most only about 100 pixels
// can be used at a time before it will take too long
// to update the pixels and IR remote codes might be
// missed.
#define PIXEL_TYPE NEO_GRB + NEO_KHZ800 // Type of NeoPixel. Keep this the default
// if unsure. See the NeoPixel library examples
// for more explanation and other possible values.
#define IR_PIN 2 // Pin connected to the IR receiver.
// Adafruit IR Remote Codes:
// Button Code Button Code
// ----------- ------ ------ -----
// VOL-: 0x0000 0/10+: 0x000C
// Play/Pause: 0x0001 1: 0x0010
// VOL+: 0x0002 2: 0x0011
// SETUP: 0x0004 3: 0x0012
// STOP/MODE: 0x0006 4: 0x0014
// UP: 0x0005 5: 0x0015
// DOWN: 0x000D 6: 0x0016
// LEFT: 0x0008 7: 0x0018
// RIGHT: 0x000A 8: 0x0019
// ENTER/SAVE: 0x0009 9: 0x001A
// Back: 0x000E
#define COLOR_CHANGE 0x000A // Button that cycles through color animations.
#define ANIMATION_CHANGE 0x0008 // Button that cycles through animation types (only two supported).
#define SPEED_CHANGE 0x0005 // Button that cycles through speed choices.
#define POWER_OFF 0x0000 // Button that turns off/sleeps the pixels.
#define POWER_ON 0x0002 // Button that turns on the pixels. Must be pressed twice to register!
// Build lookup table/palette for the color animations so they aren't computed at runtime.
// The colorPalette two-dimensional array below has a row for each color animation and a column
// for each step within the animation. Each value is a 24-bit RGB color. By looping through
// the columns of a row the colors of pixels will animate.
const int colorSteps = 8; // Number of rows/animations.
const int colorCount = 24; // Number of columns/steps.
const uint32_t colorPalette[colorCount][colorSteps] PROGMEM = {
// Complimentary colors
{ 0xFF0000, 0xDA2424, 0xB64848, 0x916D6D, 0x6D9191, 0x48B6B6, 0x24DADA, 0x00FFFF }, // Red-cyan
{ 0xFFFF00, 0xDADA24, 0xB6B648, 0x91916D, 0x6D6D91, 0x4848B6, 0x2424DA, 0x0000FF }, // Yellow-blue
{ 0x00FF00, 0x24DA24, 0x48B648, 0x6D916D, 0x916D91, 0xB648B6, 0xDA24DA, 0xFF00FF }, // Green-magenta
// Adjacent colors (on color wheel).
{ 0xFF0000, 0xFF2400, 0xFF4800, 0xFF6D00, 0xFF9100, 0xFFB600, 0xFFDA00, 0xFFFF00 }, // Red-yellow
{ 0xFFFF00, 0xDAFF00, 0xB6FF00, 0x91FF00, 0x6DFF00, 0x48FF00, 0x24FF00, 0x00FF00 }, // Yellow-green
{ 0x00FF00, 0x00FF24, 0x00FF48, 0x00FF6D, 0x00FF91, 0x00FFB6, 0x00FFDA, 0x00FFFF }, // Green-cyan
{ 0x00FFFF, 0x00DAFF, 0x00B6FF, 0x0091FF, 0x006DFF, 0x0048FF, 0x0024FF, 0x0000FF }, // Cyan-blue
{ 0x0000FF, 0x2400FF, 0x4800FF, 0x6D00FF, 0x9100FF, 0xB600FF, 0xDA00FF, 0xFF00FF }, // Blue-magenta
{ 0xFF00FF, 0xFF00DA, 0xFF00B6, 0xFF0091, 0xFF006D, 0xFF0048, 0xFF0024, 0xFF0000 }, // Magenta-red
// Other combos.
{ 0xFF0000, 0xDA2400, 0xB64800, 0x916D00, 0x6D9100, 0x48B600, 0x24DA00, 0x00FF00 }, // Red-green
{ 0xFFFF00, 0xDAFF24, 0xB6FF48, 0x91FF6D, 0x6DFF91, 0x48FFB6, 0x24FFDA, 0x00FFFF }, // Yellow-cyan
{ 0x00FF00, 0x00DA24, 0x00B648, 0x00916D, 0x006D91, 0x0048B6, 0x0024DA, 0x0000FF }, // Green-blue
{ 0x00FFFF, 0x24DAFF, 0x48B6FF, 0x6D91FF, 0x916DFF, 0xB648FF, 0xDA24FF, 0xFF00FF }, // Cyan-magenta
{ 0x0000FF, 0x2400DA, 0x4800B6, 0x6D0091, 0x91006D, 0xB60048, 0xDA0024, 0xFF0000 }, // Blue-red
{ 0xFF00FF, 0xFF24DA, 0xFF48B6, 0xFF6D91, 0xFF916D, 0xFFB648, 0xFFDA24, 0xFFFF00 }, // Magenta-yellow
// Solid colors fading to dark.
{ 0xFF0000, 0xDF0000, 0xBF0000, 0x9F0000, 0x7F0000, 0x5F0000, 0x3F0000, 0x1F0000 }, // Red
{ 0xFF9900, 0xDF8500, 0xBF7200, 0x9F5F00, 0x7F4C00, 0x5F3900, 0x3F2600, 0x1F1300 }, // Orange
{ 0xFFFF00, 0xDFDF00, 0xBFBF00, 0x9F9F00, 0x7F7F00, 0x5F5F00, 0x3F3F00, 0x1F1F00 }, // Yellow
{ 0x00FF00, 0x00DF00, 0x00BF00, 0x009F00, 0x007F00, 0x005F00, 0x003F00, 0x001F00 }, // Green
{ 0x0000FF, 0x0000DF, 0x0000BF, 0x00009F, 0x00007F, 0x00005F, 0x00003F, 0x00001F }, // Blue
{ 0x4B0082, 0x410071, 0x380061, 0x2E0051, 0x250041, 0x1C0030, 0x120020, 0x090010 }, // Indigo
{ 0x8B00FF, 0x7900DF, 0x6800BF, 0x56009F, 0x45007F, 0x34005F, 0x22003F, 0x11001F }, // Violet
{ 0xFFFFFF, 0xDFDFDF, 0xBFBFBF, 0x9F9F9F, 0x7F7F7F, 0x5F5F5F, 0x3F3F3F, 0x1F1F1F }, // White
// Rainbow colors.
{ 0xFF0000, 0xFF9900, 0xFFFF00, 0x00FF00, 0x0000FF, 0x4B0082, 0x8B00FF, 0xFFFFFF }
};
// List of animations speeds (in milliseconds). This is how long an animation spends before
// changing to the next step. Higher values are slower.
const uint16_t speeds[5] = { 400, 200, 100, 50, 25 };
// Global state used by the sketch:
Adafruit_NeoPixel strip = Adafruit_NeoPixel(PIXEL_COUNT, PIXEL_PIN, PIXEL_TYPE);
volatile bool receiverFell = false;
uint8_t colorIndex = 0;
uint8_t animationIndex = 0;
uint8_t speedIndex = 2;
void setup(void) {
// Setup IR receiver pin as an input.
pinMode(IR_PIN, INPUT);
// Initialize and clear the NeoPixel strip.
strip.begin();
strip.clear();
strip.show(); // Initialize all pixels to 'off'
// Enable an interrupt that's called when the IR receiver pin signal falls
// from high to low. This indicates a remote control code being received.
attachInterrupt(0, receiverFalling, FALLING);
}
void loop(void) {
// Main loop will first update all the pixels based on the current animation.
for (int i = 0; i < PIXEL_COUNT; ++i) {
switch (animationIndex) {
case 0:
{
// Animation 0, solid color pulse of all pixels.
uint8_t currentStep = (millis()/speeds[speedIndex])%(colorSteps*2-2);
if (currentStep >= colorSteps) {
currentStep = colorSteps-(currentStep-(colorSteps-2));
}
// Note that colors are stored in flash memory so they need to be read
// using the pgmspace.h functions.
uint32_t color = pgm_read_dword_near(&colorPalette[colorIndex][currentStep]);
strip.setPixelColor(i, color);
break;
}
case 1:
{
// Animation 1, moving color pulse. Use position to change brightness.
uint8_t currentStep = (millis()/speeds[speedIndex]+i)%(colorSteps*2-2);
if (currentStep >= colorSteps) {
currentStep = colorSteps-(currentStep-(colorSteps-2));
}
// Note that colors are stored in flash memory so they need to be read
// using the pgmspace.h functions.
uint32_t color = pgm_read_dword_near(&colorPalette[colorIndex][currentStep]);
strip.setPixelColor(i, color);
break;
}
}
}
// Next check for any IR remote commands.
handleRemote();
// Show the updated pixels.
strip.show();
// Again check for IR remote commands.
handleRemote();
}
void receiverFalling() {
// Interrupt function that's called when the IR receiver pin falls from high to
// low and indicates the start of an IR command being received. Note that
// interrupts need to be very fast and perform as little processing as possible.
// This just sets a global variable which the main loop will periodically check
// and perform appropriate IR command decoding when necessary.
receiverFell = true;
}
bool readNEC(uint16_t* result) {
// Check if a NEC IR remote command can be read and decoded from the IR receiver.
// If the command is decoded then the result is stored in the provided pointer and
// true is returned. Otherwise if the command was not decoded then false is returned.
// First check that a falling signal was detected and start reading pulses.
if (!receiverFell) {
return false;
}
// Read the first pulse with a large timeout since it's 9ms long, then
// read subsequent pulses with a shorter 2ms timeout.
uint32_t durations[33];
durations[0] = pulseIn(IR_PIN, HIGH, 20000);
for (uint8_t i = 1; i < 33; ++i) {
durations[i] = pulseIn(IR_PIN, HIGH, 5000);
}
// Reset any state changed by the interrupt.
receiverFell = false;
// Check the received pulses are in a NEC format.
// First verify the initial pulse is around 4.5ms long.
if ((durations[0] < 4000) || (durations[1] > 5000)) {
return false;
}
// Now read the bits from subsequent pulses. Stop if any were a timeout (0 value).
uint8_t data[4] = {0};
for (uint8_t i=0; i<32; ++i) {
if (durations[1+i] == 0) {
return false; // Timeout
}
uint8_t b = durations[1+i] < 1000 ? 0 : 1;
data[i/8] |= b << (i%8);
}
// Verify bytes and their inverse match. Use the same two checks as the NEC IR remote
// library here: https://github.com/adafruit/Adafruit-NEC-remote-control-library
if ((data[0] == (~data[1] & 0xFF)) && (data[2] == (~data[3] & 0xFF))) {
*result = data[0] << 8 | data[2];
return true;
}
else if ((data[0] == 0) && (data[1] == 0xBF) && (data[2] == (~data[3] & 0xFF))) {
*result = data[2];
return true;
}
else {
// Something didn't match, fail!
return false;
}
}
void handleRemote() {
// Check if an IR remote code was received and perform the appropriate action.
// First read a code.
uint16_t irCode;
if (!readNEC(&irCode)) {
return;
}
switch(irCode) {
case COLOR_CHANGE:
// Color change command, increment the current color animation and wrap
// back to zero when past the end.
colorIndex = (colorIndex+1)%colorCount;
break;
case ANIMATION_CHANGE:
// Animation change command, increment the current animation type and wrap
// back to zero when past the end.
animationIndex = (animationIndex+1)%2;
break;
case SPEED_CHANGE:
// Speed change command, increment the current speed and wrap back to zero
// when past the end.
speedIndex = (speedIndex+1)%5;
break;
case POWER_OFF:
// Enter full power down sleep mode.
// First turn off the NeoPixels.
strip.clear();
strip.show();
while (true) {
// Next disable the current falling interrupt and enable a low value interrupt.
// This is required because the ATtiny85 can't wake from a falling interrupt
// and instead can only wake from a high or low value interrupt.
detachInterrupt(0);
attachInterrupt(0, receiverFalling, LOW);
// Now enter full power down sleep mode.
power_all_disable();
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
sleep_mode();
// Processor is currently asleep and will wake up when the IR receiver pin
// is at a low value (i.e. when a IR command is received).
// Sleep resumes here. When awake power everything back up.
power_all_enable();
// Re-enable the falling interrupt.
detachInterrupt(0);
attachInterrupt(0, receiverFalling, FALLING);
// Now wait up to 1 second for a second power on command to be received.
// This is necessary because the first power on command will wake up the CPU
// but happens a little too quickly to be reliably read.
for (int i=0; i<200; ++i) {
uint16_t irCode;
if ((readNEC(&irCode) == 1) && (irCode == POWER_ON)) {
// Second power on command was received, jump out of the power on loop and
// return to normal operation.
return;
}
delay(5);
}
// If no power on command was received within 1 second of awaking then the
// code will loop back to the top and go to sleep again.
}
}
}