Application Programming

Joel Gustafson

You can generate random HTMaA projects by picking an input device and an output device and thinking about what it could mean to wire them together. This week, I picked an accelerometer and a speaker and made an instrument.

I originally thought of making a sort of theremin, but dead reckoning absolute position with an accelerometer turns out to be almost impossible. I can't reliably integrate over noisy data, so I have to be happy with second derivatives. This means this instrument is more like a conductor's baton, where the information is captured in relative motion, not actual position.

The board I made was a minimal merge of the hello.ADXL343 and the hello.speaker.45. I used the ADXL343 Eagle library from SparkFun and planned for the 0.25 watt, 8-Ohm speakers that I found in the architecture shop. I did the math and with a 3.3v regulator, I'd need an additional ~30 Ohms of resistance to hit 0.25 watts for the speaker, so I laid out three parallel 100-ohm resistors on the other side of the MOSFET.

I missed the reflow soldering recitation, but the accelerometer didn't seem that small, so I soldered it by hand. It took two tries to get it working, but I think the first one didn't work because the soldering iron was too hot, not (only) because I missed connections.

The second board I made worked perfectly with both accelerometer input and speaker output.

For speaker output I used the ATtiny's Timer0 with a prescale of 1024.

// Timers
cli();
TCCR0B |= ((1 << CS02) | (1 << CS00)); // Timer 0 prescaling - divides by 1024 */
TCCR0A |= (1 << WGM01);
OCR0A = 50;
TIMSK |= (1 << OCIE0A);
DDRB |= (1 << PB1);
PORTB |= (1 << PB1);
sei();

... And on every cycle toggled the speaker (this means it's making a square wave, which is not ideal, but it was easy so I'll take it).

ISR(TIMER0_COMPA_vect)
{
  PORTB ^= (1 << PB1);
}

I copied the I2C code from the accelerometer example but stripped away the serial communication part. I didn't have time to come up with something clever to do with the actual sensor values, so I just decided to use the norm of the magnitudes on all axes v = x^2 + y^2 + z^2 and scale v down to a human-frequency-range value. After a bit of fiddling (but not bit-fiddling, mind you) I settled on this arbitrary code:

value = ((int) data[0] * (int) data[0]) + ((int) data[2] * (int) data[2]) + ((int) data[4] * (int) data[4]);
filter = (1 - EPS) * filter + (EPS * value);
if (++counter > 100) {
  counter = 0;
  OCR0A = (unsigned char) round(filter / 100);
}

Hooray!