MCN 2016 Workshop
At today’s workshop, we’ll follow a series of small projects using parts from our kit. We’ll copy and paste code to see what it does. Then later, we’ll dive into the inner working of the code. Finally, we’ll make something on our own with the combined parts.
If you prefer to download all the files at once, please download this .zip file.
Installing the Software
- Download the latest Arduino IDE software.
- Follow the Adafruit instructions to install the software and the Adafruit boards. Careful. Windows can be tricky.
- We will also be using NeoPixels. Install the library with the instructions here. Look for the numbered list at the top of the page.
Review the Hardware Kit
Let’s take a look at the parts we have:
- Wearable Arduino-type Board: Gemma Starter Kit
- CR2032 Coin Batteries
- Tactile On/Off Switch with Leads
- Vibrating Mini Motor Disc
- Piezo buzzer
- PDS Photoresistor
- 100K Linear Potentiometer
- 1 kΩ Resistors
- 270 Ω Resistors
- NPN Bipolar Transistor (PN2222) – powerful little electronic switches, to control medium-power electronics such as small motors, solenoids, or IR LEDs.
- Misc LEDs
- 1N4001 diode – used for kickback protection (place across solenoids, relays, & DC motors to safely discharge the spikes generated by the coils)
Learn More:
- Learn more about the Arduino Pro here.
- Learn more about FLORA (the larger version of Gemma)
- Learn more about NeoPixels here
Configure Arduino IDE
There are a few settings we need to change to use the Gemmas.
First, choose Tools >> Board >> Adafruit Gemma 8MHz
Next, choose Tools >> Programmer >> USBtinyISP
When you are ready to upload code, you’ll need to trigger the bootloader. Press the little push button on the Gemma. The LED will pulse showing it’s ready to accept code.
Hardware Test
Use the USB cable to connect the Gemma to your computer. You don’t need to connect anything else.
Copy and paste the following code (all code, use select all) into the Arduino IDE software.
/* Blink Turns on an LED on for one second, then off for one second, repeatedly. This example code is in the public domain. To upload to your or Trinket: 1) Select the proper board from the Tools->Board Menu (Arduino Gemma if teal, Adafruit Gemma if black) 2) Select the uploader from the Tools->Programmer ("Arduino Gemma" if teal, "USBtinyISP" if black Gemma) 3) Plug in the Gemma into USB, make sure you see the green LED lit 4) For windows, make sure you install the right Gemma drivers 5) Press the button on the Gemma/Trinket - verify you see the red LED pulse. This means it is ready to receive data 6) Click the upload button above within 10 seconds */ int led = 1; // blink 'digital' pin 1 - AKA the built in red LED // the setup routine runs once when you press reset: void setup() { // initialize the digital pin as an output. pinMode(led, OUTPUT); } // the loop routine runs over and over again forever: void loop() { digitalWrite(led, HIGH); delay(1000); digitalWrite(led, LOW); delay(1000); }
- Press the reset button on the Arduino to start the bootloader (it will slowly flash for up to 10 seconds)
- Press the upload button
in the Arduino IDE. Watch the dialog to make sure it upload properly.
- See what happens
Blink Take 2
Let’s make our first circuit. Unplug your Gemma. Then use your hardware kit to match the following diagram. This is using a 270 ohm resistor (it has a purple line on it).
No need to change your code. Just plug in the Gemma to power it.
Did it work? Now try unplugging your Gemma, and switch out the LED for the motor. Plug back in the Gemma? Does the motor work too?
NeoPixels
Next, we’ll try upgrading the light output experience to NeoPixels. Unplug your Gemma. Take out one NeoPixel. Look for the arrow direction on the NeoPixel. Orient the NeoPixel, and create this circuit. Then upload the new code.
#include <Adafruit_NeoPixel.h> #define PIN 0 const uint8_t pxls = 1; // number of pixels in the neopixel ring const int maxBright = 64; // max brightness (0-255) // Parameter 1 = number of pixels in strip // Parameter 2 = Arduino pin number (most are valid) // Parameter 3 = pixel type flags, add together as needed: // NEO_KHZ800 800 KHz bitstream (most NeoPixel products w/WS2812 LEDs) // NEO_KHZ400 400 KHz (classic 'v1' (not v2) FLORA pixels, WS2811 drivers) // NEO_GRB Pixels are wired for GRB bitstream (most NeoPixel products) // NEO_RGB Pixels are wired for RGB bitstream (v1 FLORA pixels, not v2) Adafruit_NeoPixel strip = Adafruit_NeoPixel(pxls, PIN, NEO_GRB + NEO_KHZ800); // IMPORTANT: To reduce NeoPixel burnout risk, add 1000 uF capacitor across // pixel power leads, add 300 - 500 Ohm resistor on first pixel's data input // and minimize distance between Arduino and first pixel. Avoid connecting // on a live circuit...if you must, connect GND first. void setup() { strip.begin(); strip.setBrightness(maxBright); strip.show(); // Initialize all pixels to 'off' } void loop() { // Some example procedures showing how to display to the pixels: colorWipe(strip.Color(255, 0, 0), 50); // Red colorWipe(strip.Color(0, 255, 0), 50); // Green colorWipe(strip.Color(0, 0, 255), 50); // Blue // Send a theater pixel chase in... theaterChase(strip.Color(127, 127, 127), 50); // White theaterChase(strip.Color(127, 0, 0), 50); // Red theaterChase(strip.Color( 0, 0, 127), 50); // Blue rainbow(20); rainbowCycle(20); theaterChaseRainbow(50); } // Fill the dots one after the other with a color void colorWipe(uint32_t c, uint8_t wait) { for(uint16_t i=0; i<strip.numPixels(); i++) { strip.setPixelColor(i, c); strip.show(); delay(wait); } } 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); } else if(WheelPos < 170) { WheelPos -= 85; return strip.Color(0, WheelPos * 3, 255 - WheelPos * 3); } else { WheelPos -= 170; return strip.Color(WheelPos * 3, 255 - WheelPos * 3, 0); } }
Next, try adding more pixels in series. You’ll need to change the number of pixels in the code, line 4.
Control Color Hue
/* * Code created by Chris Evans @drumminhands * Used to map a potentiometer to color hue * */ // sensor input from http://www.arduino.cc/en/Tutorial/AnalogInput // NeoPixels from here: http://learn.adafruit.com/adafruit-neopixel-uberguide/overview #include <Adafruit_NeoPixel.h> //constansts won't change const uint8_t neoPin = 0; // Neopixels Pin const uint8_t pxls = 4; // number of pixels in the neopixel ring const uint8_t maxBright = 64; // the default brightness between 0 and 255 // variables that will change int sensorPin = 1; // select the input pin for the potentiometer (analog 1 is digital 2) int sensorValue = 0; // variable to store the value coming from the sensor int neoPixelHue = 0; // stores current hue of the neoPixels during the rainbow cycles // Parameter 1 = number of pixels in strip // Parameter 2 = Arduino pin number (most are valid) // Parameter 3 = pixel type flags, add together as needed: // NEO_KHZ800 800 KHz bitstream (most NeoPixel products w/WS2812 LEDs) // NEO_KHZ400 400 KHz (classic 'v1' (not v2) FLORA pixels, WS2811 drivers) // NEO_GRB Pixels are wired for GRB bitstream (most NeoPixel products) // NEO_RGB Pixels are wired for RGB bitstream (v1 FLORA pixels, not v2) Adafruit_NeoPixel strip = Adafruit_NeoPixel(pxls, neoPin, NEO_GRB + NEO_KHZ800); // IMPORTANT: To reduce NeoPixel burnout risk, add 1000 uF capacitor across // pixel power leads, add 300 - 500 Ohm resistor on first pixel's data input // and minimize distance between Arduino and first pixel. Avoid connecting // on a live circuit...if you must, connect GND first. void setup() { strip.begin(); strip.setBrightness(maxBright); // initialize brightness strip.show(); // Initialize all pixels to 'off' } void loop() { sensorValue = analogRead(sensorPin); // read the value from the sensor sensorValue = map(sensorValue, 0, 1023, 0, 255); // change value porportionally to be between 0 and 255 colorSet(Wheel(sensorValue)); // set the color based on the pot } // sets all pixels to one given color void colorSet(uint32_t c){ for(uint16_t i=0; i<strip.numPixels(); i++) { strip.setPixelColor(i, c); } strip.show(); } // 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); } else if(WheelPos < 170) { WheelPos -= 85; return strip.Color(0, WheelPos * 3, 255 - WheelPos * 3); } else { WheelPos -= 170; return strip.Color(WheelPos * 3, 255 - WheelPos * 3, 0); } }
BREAK?
Morse Code
Now, let’s unplug everything and wire up a circuit using the motor.
// by chris@drumminhands.com // send morse code signals to the motor // find morse code charts here: https://en.wikipedia.org/wiki/Morse_code int m = 1; // motor pin int dotDuration = 250; // duration of a morse code dot in miliseconds int dashDuration = 3*dotDuration; // duration of a morse code dash in miliseconds int delayBetweenChars = 250; // pause duration between buzzing each character // the setup routine runs once when you press reset: void setup() { // initialize the digital pin as an output. pinMode(m, OUTPUT); } // the loop routine runs over and over again forever: void loop() { // s dot(); dot(); dot(); delay(500); // o dash(); dash(); dash(); delay(500); // s dot(); dot(); dot(); delay(2000); } void buzz(int p, int d){ // buzzes the motor connected to pin p, for duration d (in miliseconds) digitalWrite(p, HIGH); delay(d); digitalWrite(p, LOW); delay(delayBetweenChars); } void dot(){ // buzzes the motor for a short pulse buzz(m,dotDuration); } void dash(){ buzz(m,dashDuration); // three times a dash }
Mindfulness Bracelet
We’ve done enough little tests with your Arduino kit. It’s time to do a bigger project. If you want to follow the official instructions, see the Adafruit tutorial on making a Mindfulness Bracelet.
Start by trying the circuit with your alligator clips. Careful to wire everything correctly. And the wires on the buzzer are tiny, so double-check that you have a good connection.
This code is set to buzz every five seconds while we’re testing. Change the value in line 8 from 5 to a more reasonable duration. Say 60 minutes. Or 120.
// Mindfulness Bracelet sketch for Adafruit/Arduino Gemma. Briefly runs // vibrating motor (connected through transistor) at regular intervals. // This code is not beginner-friendly, it does a lot of esoteric low-level // hardware shenanigans in order to conserve battery power. const uint32_t // These may be the only lines you need to edit... onTime = 2 * 1000L, // Vibration motor run time, in milliseconds interval = 5 * 1000L; // Time between reminders, in milliseconds // It gets progressively geekier from here... // Additional power savings can optionally be realized by disabling the // power-on LED, either by desoldering or by cutting the trace from 3Vo // on the component side of the board. // This sketch spends nearly all its time in a low-power sleep state... #include <avr/power.h> #include <avr/sleep.h> // The chip's 'watchdog timer' (WDT) is used to wake up the CPU when needed. // WDT runs on its own 128 KHz clock source independent of main CPU clock. // Uncalibrated -- it's "128 KHz-ish" -- thus not reliable for extended // timekeeping. To compensate, immediately at startup the WDT is run for // one maximum-duration cycle (about 8 seconds...ish) while keeping the CPU // awake, the actual elapsed time is noted and used as a point of reference // when calculating sleep times. Still quite sloppy -- the WDT only has a // max resolution down to 16 ms -- this may drift up to 30 seconds per hour, // but is an improvement over the 'raw' WDT clock and is adequate for this // casual, non-medical, non-Mars-landing application. Alternatives would // require keeping the CPU awake, draining the battery much quicker. uint16_t maxSleepInterval; // Actual ms in '8-ish sec' WDT interval volatile uint32_t sleepTime = 1; // Total milliseconds remaining in sleep volatile uint16_t sleepInterval = 1; // ms to subtract in current WDT cycle volatile uint8_t tablePos = 0; // Index into WDT configuration table void setup() { // Unused pins can be set to INPUT w/pullup -- most power-efficient state pinMode(0, INPUT_PULLUP); pinMode(2, INPUT_PULLUP); // LED shenanigans. Rather that setting pin 1 to an output and using // digitalWrite() to turn the LED on or off, the internal pull-up resistor // (about 10K) is enabled or disabled, dimly lighting the LED with much // less current. pinMode(1, INPUT); // LED off to start // AVR peripherals that are NEVER used by the sketch are disabled to save // tiny bits of power. Some have side-effects, don't do this willy-nilly. // If using analogWrite() to for different motor levels, timer 0 and/or 1 // must be enabled -- for power efficiency they could be turned off in the // ubersleep() function and re-enabled on wake. power_adc_disable(); // Knocks out analogRead() power_timer1_disable(); // May knock out analogWrite() power_usi_disable(); // Knocks out TinyWire library DIDR0 = _BV(AIN1D) | _BV(AIN0D); // Digital input disable on analog pins // Timer 0 isn't disabled yet...it's needed for one thing first... // The aforementioned watchdog timer calibration... uint32_t t = millis(); // Save start time noInterrupts(); // Timing-critical... MCUSR &= ~_BV(WDRF); // Watchdog reset flag WDTCR = _BV(WDCE) | _BV(WDE); // WDT change enable WDTCR = _BV(WDIE) | _BV(WDP3) | _BV(WDP0); // 8192-ish ms interval interrupts(); while(sleepTime); // Wait for WDT maxSleepInterval = millis() - t; // Actual ms elapsed maxSleepInterval += 64; // Egyptian constant power_timer0_disable(); // Knocks out millis(), delay(), analogWrite() } const uint32_t offTime = interval - onTime; // Duration motor is off, ms void loop() { pinMode(1, INPUT_PULLUP); // LED on (using internal pullup) ubersleep(onTime); // Delay while LED/motor on pinMode(1, INPUT); // LED off ubersleep(offTime); // Delay while off } // WDT timer operates only in specific intervals based on a prescaler. // CPU wakes on each interval, prescaler is adjusted as needed to pick off // the longest setting possible on each pass, until requested milliseconds // have elapsed. const uint8_t cfg[] PROGMEM = { // WDT config bits for different intervals _BV(WDIE) | _BV(WDP3) | _BV(WDP0), // ~8192 ms _BV(WDIE) | _BV(WDP3) , // ~4096 ms _BV(WDIE) | _BV(WDP2) | _BV(WDP1) | _BV(WDP0), // ~2048 ms _BV(WDIE) | _BV(WDP2) | _BV(WDP1) , // ~1024 ms _BV(WDIE) | _BV(WDP2) | _BV(WDP0), // ~512 ms _BV(WDIE) | _BV(WDP2) , // ~256 ms _BV(WDIE) | _BV(WDP1) | _BV(WDP0), // ~128 ms _BV(WDIE) | _BV(WDP1) , // ~64 ms _BV(WDIE) | _BV(WDP0), // ~32 ms _BV(WDIE) // ~16 ms }; // Remember, WDT clock is uncalibrated, times are "ish" void ubersleep(uint32_t ms) { if(ms == 0) return; tablePos = 0; // Reset WDT config stuff to sleepInterval = maxSleepInterval; // longest interval to start configWDT(ms); // Set up for requested time set_sleep_mode(SLEEP_MODE_PWR_DOWN); // Deepest sleep mode sleep_enable(); while(sleepTime && (tablePos < sizeof(cfg))) sleep_mode(); noInterrupts(); // WDT off (timing critical)... MCUSR &= ~_BV(WDRF); WDTCR = 0; interrupts(); } static void configWDT(uint32_t newTime) { sleepTime = newTime; // Total sleep time remaining (ms) // Find next longest WDT interval that fits within remaining time... while(sleepInterval > newTime) { sleepInterval /= 2; // Each is 1/2 previous if(++tablePos >= sizeof(cfg)) return; // No shorter intervals } uint8_t bits = pgm_read_byte(&cfg[tablePos]); // WDT config bits for time noInterrupts(); // Timing-critical... MCUSR &= ~_BV(WDRF); WDTCR = _BV(WDCE) | _BV(WDE); // WDT change enable WDTCR = bits; // Interrupt + prescale interrupts(); } ISR(WDT_vect) { // Watchdog timeout interrupt configWDT(sleepTime - sleepInterval); // Subtract, setup next cycle... }
BREAK?
Remix Ideas
- Change the color of the pixels based on the light sensor.
- Pimp out your name badge with an LED sequence.
- Try using both the button and potentiometer with the NeoPixels. See code in the “other” folder in the download.
Finish Your Product
- When you have a curcuit working they way you want it to, now it’s time to solder or sew.
- Leave plenty of time to cut, glue, solder, sew, etc.
- Try cutting a table tennis ball in half and use it as a diffuser on your LEDs.
- Cut felt to make a bracelet or modify your name badge
- Finish up your mindfulness bracelet and wear it for the rest of the conference. Remember to get up and move.