UNTZtrument is just the start. We hope you’ll hack it to your heart’s content. Some ideas:
- New types of MIDI musical instruments or sequencers.
- Beyond music…UNTZtrument could trigger video clips and effects for live “veejay” performances.
- The Arduino Leonardo can also act as a HID keyboard or mouse…how about a game controller or an assistive device?
Not just code…there’s a little space for hardware hacking as well:
- Encoders or potentiometers could be added to control different music parameters (volume, tempo, etc.).
- A popular mod for the Arduinome (an inspiration for the UNTZtrument) is an accelerometer, for more expressive performance.
- “Bling it up” with internal LEDs.
UNTZtrument hacking is not for the meek; it requires patience, tools and a willingness to improvise. Tolerances inside the case are very tight. If you’ve done “circuit bending” before (modifying electronic instruments), you can probably handle this.
Hardware Considerations
SDA and SCL are connected to digital pins 2 and 3 on the Arduino Leonardo. Those pins are not available as inputs or outputs. Everything else is fair game!
A USB port provides current up to 500 milliamps. UNTZtrument uses about 150 mA, allowing up to 350 mA for your own additions (but aim a little lower to play it safe). That’s enough for a few NeoPixels or other LEDs, or a small piezo buzzer or vibration motor for feedback. HELLA UNTZtrument uses close to 300 mA, so there’s about 200 mA available for adding your own bling.
Small slots between each of the “cells” in the UNTZtrument case can be used for routing wires. Unruly wiring can be kept at bay with some clear tape or a dot of hot glue.
There are three available GND pins on the Arduino board, one of which is used by the Trellis. Because we’re using USB for power, both 5V and VIN provide 5 Volts (VIN is normally the higher unregulated voltage from the DC jack, but that’s not being used here); UNTZtrument uses one of these 5V pins, leaving the other available. If you need to split power in more directions, 3-way and 5-way block connectors are available, as are smaller 2-way cold splices.
Acrylic requires special techniques for drilling; normal drill bits will crack it! This optional plastic part (separately available from Adafruit) has cutouts for adding encoders or potentiometers.
Three sides of UNTZtrument are identical, so you can add controls to any (or all) sides. On the HELLA UNTZtrument, these fit only on the left and right sides, or there’s a longer front part specifically for the HELLA.
If you have access to a laser cutter, the UNTZtrument case design can be downloaded from the last page of this guide, so you can make any special cutouts you like!
If you want increasing values when turning clockwise, and if using the 10K pots in the Adafruit shop, wire them as shown here.
Other pots may behave differently. If the output is reversed from what you wanted, just swap the outer connections, or compensate in code using the map() function.
You need to be REALLY CAREFUL when doing this. DO NOT straighten the existing bends — the legs will crack off! Instead, add extra bends a little further down.
Potentiometers especially are a tight squeeze.
In the second photo you can see all of the hackishness that was required for one project. A clamp provides a temporary hold while working. Hot glue keeps loose wires at bay. Cold splices provide multiple +5V and GND connections.
Software Considerations
If adding I2C devices (such as certain accelerometers or other sensors), be aware that our example code does a crass thing to activate 400 KHz I2C (this makes the LED updates a bit more responsive), and it might not be compatible with other devices you’re interfacing. If this creates a problem, one option is simply to disable this line of code, reverting to the standard 100 KHz communication.
On 8-bit AVR boards, the offending line, commented out, would be:
// TWBR = 12;
On 32-bit SAMD-based boards (e.g. M0), the line in question is:
// Wire.setClock(400000L);
Another option (on AVR) is to save the original value of the TWBR register in a global variable following initialization, then set it to the 100 or 400 KHz rates as needed.
Global variable, before setup():
uint8_t i2c_save;
i2c_save = TWBR; // Original 100 KHz value
TWBR = 12; // 400 KHz untztrument.writeDisplay(); … TWBR = i2c_save; // 100 KHz // Read/write other I2C devices here
These are architecture-specific hacks! If you’re thinking of adapting the UNTZtrument code to any other microcontroller, don’t do this, it’s non-portable and may be more trouble than it’s worth.
Using the UNTZtrument Arduino Library
The Adafruit_UNTZtrument library provides a few utilities to help with creating UNTZtrument-specific sketches. We foresee every application being quite distinctive, so the library is pretty minimal and doesn’t impose a lot of policy.
To use the library, you need to include three header files:
#include <Wire.h> #include <Adafruit_Trellis.h> #include <Adafruit_UNTZtrument.h>
The first enables I2C communication. Second is the core Trellis library for reading button presses and changing the LEDs. The third has our UNTZtrument-specific utilities.
The Adafruit_UNTZtrument library contains two object classes. The first is called Adafruit_UNTZtrument and is simply a slight extension of the Adafruit_TrellisSet class from the Adafruit_Trellis library…in fact, declaring and initializing the Adafruit_UNTZtrument object is identical to a TrellisSet:
Adafruit_Trellis T[4]; Adafruit_UNTZtrument untztrument(&T[0], &T[1], &T[2], &T[3]); const uint8_t addr[] = { 0x70, 0x71, 0x72, 0x73 }; void setup() { untztrument.begin(addr[0], addr[1], addr[2], addr[3]); }
Four Adafruit_Trellis objects are declared (eight for a HELLA UNTZtrument) and passed to the UNTZtrument constructor. Their I2C addresses are passed to the begin() function in setup(). All the same Adafruit_Trellis or Adafruit_TrellisSet functions are then available to the Adafruit_UNTZtrument object.
The Adafruit_UNTZtrument object provides two new functions for converting between button/LED index numbers (as normally used by the Trellis library) and (X,Y) coordinates (more useful for programs like the step sequencer).
i2xy() converts a Trellis button or LED index (0–63 for an 8x8 UNTZtrument, or 0–127 for a HELLA UNTZtrument) to separate X (column) and Y (row) values. You must provide the addresses of two unsigned char (or uint8_t) variables to store the results:
uint8_t x, y; untztrument.i2xy(i, &x, &y);
uint8_t i = untztrument.xy2i(x, y);
Adafruit_UNTZtrument also provides the enc object for interfacing quadrature encoders. Unlike analog potentiometers (which have a set range of 0–1023 using analogRead()), encoders are more flexible (and they can spin around and around, no end stops).
Each encoder requires two pins (sometimes called the “A” and “B” channels). The third pin connects to GND. The shaft “click” function of some encoders is not explicitly handled by the library…but it works just like any normally-open contact switch and is pretty straightforward to code for.
Do not connect encoders to Leonardo pins 2 or 3 — those link up to the SDA and SCL pins required for I2C communication. Anything else is fair game though (even pins 0 and 1, which Arduino Uno coders often have to avoid).
To use an encoder, just pass the index of the A and B pins to the constructor:
// You can declare encoders onesy-twosey: enc e(4, 5); // Or create a whole array of them: enc e[] = { enc( 4, 5), enc( 6, 7), enc( 8, 9), enc(12, A2) };
The encoder pins do not need to be sequential, nor are they limited to pin-change-interrupt pins. Any two pins will do.
An optional third parameter to the enc() constructor enables or disables the Arduino’s internal pull-up resistor on that pin. Some encoders (such as the ones previously shown) use the pull-ups…this is the default case and the third parameter is not needed. Others (“active” encoders, requiring a 5V connection) drive their output high or low on their own; the pull-up is not needed. Pass a third parameter of false when using these. This is rare.
In your setup() function, you must initialize the encoders as follows:
void setup() { // Initialize any/all previously-declared encoders: enc::begin(); }
The encoders need to be frequently polled in order to update their values (this is the downside to not using interrupt pins). There are a couple of ways to do this. Simplest is just to put this line in your loop() function:
void loop() { enc::poll(); }
This requires the loop() function to iterate quickly. But if your program occasionally has to do something more time-consuming, the encoders may “lose counts” (the value won’t change as the knob is turned). To avoid this, you can use a timer interrupt to call enc::poll() at regular intervals (e.g. once per millisecond). This requires some homework…the library does not provide this — timers are a limited resource and every application or timer library sets its own rule for these — so you’ll have to wrap a bit of your own code around it.
To read the value from an encoder, use the getValue() function:
// Reading from one encoder named "e": int16_t bpm = e.getValue(); // Reading one encoder in an enc array: int16_t bpm = e[0].getValue();
// Limit encoder "e" to 0-100 (inclusive): e.setBounds(0, 100);
The value of an encoder can be set or changed with the setValue() function:
e.setValue(42);
e.setBounds(0, 100); e.setValue(42);
For example, let’s suppose we wanted an encoder for setting a tempo between 60 and 480 beats per minute (240 by default). We’re using an Adafruit encoder or another type with the 4-count detents, so the relevant parts of our code might look like this:
enc e(4, 5); // Encoder on pins 4 & 5 // in setup(): e.setBounds(60 * 4, 480 * 4 + 3); e.setValue(240 * 4); // in loop(): bpm = e.getValue() / 4;
I instinctively always want to say détente, but no, that’s a different word for a different thing. Detent. De-tent, like having one’s tent removed. Even Wikipedia notes this confusion. :)
Page last edited June 11, 2014
Text editor powered by tinymce.