Controlling Motors From High and Low with Arduino ( inc. CharliePlexing)

Blogged by Mathieu as Diary — Mathieu Sun 31 Aug 2008 12:24

As part of an HMC6343-based compass project, I wanted to make a haptic interface based on little vibrating motors in a hatband. Like the compass-belts that have been done before, but for your head. -)

My first prototype worked fine with some 8 LEDs, using some digitalWrite() to select which LEDs to turn on from the high side, and some analogWrite() to control how brightly from the low side, like charlieplexing. I was able to create a cylon LED ramp that smoothly indicated how the compass was pointing.

To replace the LEDs with vibrating motors, however, I needed some additional electronics since they eat roughly 80mA at 3.3V each (I’m using a 3.3V volt Arduino, the Wee, because the sensor is 3.3V and that saves me a lot of grief on level shifting on I2C), and that is way too much for Arduino. So after some research and some simulations on LTspice (free, get yourself a copy today!) here’s what I came up with:

Circuit Diagram

According to the simulations, this will draw uAmps from Arduino while driving the motors at nearly their full 80mA. You can generalize the concept to charlieplex motors just like you would LEDs, I think.

Going on vacation, so won’t be able to test it with the full 8 motors just yet, but it works with just one. Of course, with a multiplexer rated for 100mA, I could do the same with one transistor per lower side PWM circuit and nothing but Vs on the high side, but I don’t have a multiplexer of any rating at the moment, and this seemed like a good alternative.

Manipulating Perceived Position of Sound in Space with an Arduino

Blogged by Mathieu as Diary — Mathieu Sun 31 Aug 2008 12:18

Before I start, kudos and thanks to Narbotic and others for all the pioneering work on sound generation. I just noticed Narbotic’s stereo beat generator… and I’ll say right up front that I have not mastered advanced methods, so all I will be doing is bit-banging some low-frequency audio. Think of this as a proof of concept.

Oh, and apologies for my long-winded style. I undercomment my code and overcomment my prose.

I was playing around with Sparkfun’s pricey HMC6343 tilt-compensated compass and after the obvious cylon LED array, needed another output method, so decided to play with a stereo beep that would seem to track a fixed azimuth as you rotated the board. Here’s the hardware on a breadboard, and mark II on protoboard:

Breadboard version

Proto Board Version

I shamelessly stole code left and right from the trailblazers and kludged together something that uses analogWrite() with a modified PWM frequency on pins 9 and 10 to generate low frequency bit-banged sound with independent volume control on right and left channels. It wasn’t pretty, but it worked, after a fashion. Alas, as you panned the audio from side to side, the effect was not as crisp as would have liked in the middle range, so I decided to do some research.

After reading this, I tried to add some delay between the two audio ‘tracks’. It works out that the delay is never greater than about 650us, so if you pick your beep’s frequency low enough, the delay can be contained inside a single cycle of your audio frequency, which is rather handy. Now I just needed to figure out how to vary the delay with the heading, and some high school geometry helped:

Breadboard version

Turns out that the delay for stereo sound is proportional to sine of that heading (measured from dead ahead). The delay for an echo from a backstop (which I have not implemented yet) is proportional to the cosine of the same angle (with a different base value, unless the backstop is somewhere inside your skull).

So was all this trig and delayMicrosecond() worth the bother? And how!

The effect is so sharp, you can tell the difference between center, ten degrees left of center and ten degrees right of center. I was so surprised, I thought it might be due to wishful thinking, so I turned off the stereo volume adjustments… even with just the delay and no change in the volume between left and right, you can hear the sound move from side to side. What’s even weirder is that if you listen to just one side, the sound stays the same.

With both volume and delay adjustments, the stereo sound effect is very noticeable, with a clear distinction around the center mark.

The code is in my previous post, but I’d appreciate it if someone tried my code out, perhaps with just a pot and analogRead() to pan left/right? Confirmation from someone else would be great.

Code for Generating Precise Stereo Sound Positions on Arduino

Blogged by Mathieu as Electronics — Mathieu Sun 31 Aug 2008 11:03

This Arduino code takes a heading from an HMC6343 electronic compass and generates a low-frequency beep that seems to always come from the south.

Explanations and hardware photos here.


// Generating a low frequency beep that seems to always come from the South
// by Mathieu Glachant @ http://ban-sidhe.com
// with shameless pilfering of much good code from the trailblazers at arduino.cc
// Public release v0.1 Labor day 2008

#include <wire.h> ;

int ASCIIoutputOn =false; //Toggles Serial ouput of measurements, note that this uses pins 0 and 1
int AudioOutputOn =true; //Toggles audio ouput of measurements

int HMC6343Address = 0x32;
int slaveAddress; // This is calculated in the setup() function

byte inByte;

byte headingData[6];
int i;

long time = 0;

int headingValue =0;
int tiltValue =0;
int rollValue =0;

int audioPinR =9; // This is the pin for the right channel audio bit-banging
int audioPinL =10; // This is the pin for the right channel audio bit-banging
// Note that these should not be in output mode unless needed,
// so they are not included in setup()

void setup()
{

TCCR1B = 0x09; // Make PWM frequency more audio friendly on pins 9 and 10

// Shift the device's documented slave address (0x32) 1 bit right
// This compensates for how the TWI library only wants the
// 7 most significant bits (with the high bit padded with 0)

slaveAddress = HMC6343Address >> 1; // This results in the 7 bit address to pass to TWI

while( millis() < 500) { delay(10); } // The HMC6343 needs a half second to start from power up

Wire.begin();

if (ASCIIoutputOn)
{
Serial.begin(9600);
Serial.print(10,BYTE);
Serial.println("Ready to start!");
Serial.print(10,BYTE);
}
}

void getSensorData()
{

Wire.beginTransmission(slaveAddress);
Wire.send(0x50); // Send a "Get Data" (0x50) command to the HMC6343
Wire.endTransmission();

delay(2); // The HMC6343 needs at least a 1ms (microsecond) delay
// after this command. Using 2ms just makes it safe

// Read the 6 heading bytes, MSB first
// The resulting 3 16bit words are the compass heading, the tilt and roll in 10th's of a degree
// For example: a heading of 1345 would be 134.5 degrees

Wire.requestFrom(slaveAddress, 6); // Request the 6 bytes of data (MSB comes first)
i = 0;
while(Wire.available() && i < 6)
{
headingData[i] = Wire.receive();
i++;
}
}

void convertDataToValues()
{
headingValue = headingData[0]*256 + headingData[1]; // Put the MSB and LSB together
tiltValue = headingData[2]*256 + headingData[3] + 900; // Put the MSB and LSB together
rollValue = headingData[4]*256 + headingData[5] + 1800; // Put the MSB and LSB together
}

void sendSerialValues()
{
Serial.print("$HTR,");
Serial.print(int (headingValue / 10)); // The whole number part of the heading
Serial.print(".");
Serial.print(int (abs (headingValue % 10))); // The fractional part of the heading
Serial.print(",");
Serial.print(int (tiltValue / 10)); // The whole number part of the tilt
Serial.print(".");
Serial.print(int (abs (tiltValue % 10))); // The fractional part of the tilt
Serial.print(",");
Serial.print(int (rollValue / 10)); // The whole number part of the roll
Serial.print(".");
Serial.print(int (abs (rollValue % 10))); // The fractional part of the roll
//Serial.print(",");
//Serial.print(ratioPin); // The remainder of the division by the number of output pins, scaled to 256
Serial.print(",A ");
//Serial.print(10,BYTE);
Serial.print(13,BYTE);
}

void stereoSound(int freq, int t, int phase, boolean phaseSign, int volumeR, int volumeL)
// freq in hz, t in ms, phase in us, phaseSign true if positive, volume from 0 to 255
{
int hperiod; //calculate 1/2 period in us
int audioPin1 =audioPinL;
int audioPin2 =audioPinR;
int volume1 =volumeL;
int volume2 =volumeR;
long cycles, i;

//pinMode(audioPinR, OUTPUT); // turn on output pin, not needed for analogWrite
//pinMode(audioPinL, OUTPUT); // turn on output pin, not needed for analogWrite

hperiod = (500000 / freq) - 7; // subtract 7 us to make up for digitalWrite overhead

cycles = ((long)freq * (long)t) / 1000; // calculate cycles

phase = max(3,phase);

if (phaseSign)
{
audioPin1 =audioPinR;
audioPin2 =audioPinL;
volume1 =volumeR;
volume2 =volumeL;
}

analogWrite(audioPin1, volume1);
for (i=0; i<= cycles; i++){ // play note for t ms
delayMicroseconds(phase);
analogWrite(audioPin2, volume2);
delayMicroseconds(hperiod-phase);
analogWrite(audioPin1, 0);
delayMicroseconds(phase);
analogWrite(audioPin2, 0);
delayMicroseconds(hperiod -phase - 1); // - 1 to make up for fractional us
analogWrite(audioPin1, volume1);
}
delayMicroseconds(hperiod);
analogWrite(audioPin1, 0);

//pinMode(audioPinR, INPUT); // shut off pin to avoid noise from other operations, not needed for analogWrite
//pinMode(audioPinL, INPUT);

}

void delayLoop(int duration)
{
while( (millis() - time) < duration) { delay(1); }
time = millis();
}

void loop()
{
getSensorData();

convertDataToValues();

if (ASCIIoutputOn) { sendSerialValues(); }

if (AudioOutputOn)
{
stereoSound(
150, // frequency of beep
40, // duration of beep
abs(int(1200*sin(float(headingValue-1800)*0.001745))), // phase shift in us
(headingValue < 1800), // phase shift sign (true if right before left)
constrain(map(headingValue,2700,1800,0,255),0,255),
constrain(map(headingValue,1800,900,255,0),0,255)
);
}

delayLoop(200); // The HMC6343 needs 200ms between measurement
// this function will make sure loop() lasts at least that long

}

Proudly powered by wordpress - Theme Back in Black 2 by neuro