Thursday, 29 September 2011
DrumHero Game
Concept:
Using the 8x8 RainbowDuino LED matrix and various vibration sensors and buttons to create a guitar hero style game for playing an electronic drumkit. This is a working example of several research areas including timers, interrupts, sensor readings, I2C bus
Mapping the LED Matrix:
To be able to manipulate the matrix it is represented in code by a 2D array containing on/off values (int 0 or 1)
int displayGrid[LEDs/2][LEDs]; // the LED matrix
initially this is initialised with all values as false. This creates a 4x8 matrix. We are dealing with 4 column as that is reasonable for a drumkit and allows each channel to occupy 2 LED's. This is much easier to view.
From here the Rainbow duino's buffer is written by reading each value of the array and colouring in the matching pixel.
void refreshDisplay() {
sendCMD(0x10, CMD_CLEAR_PAPER);
for(int x=0; x<LEDs/2; x++) {
for (int y=0; y<LEDs; y++) {
if (displayGrid[x][y] == 1) {
if (y==0) {
sendCMD(0x10, CMD_SET_INK, 0xF, 0x0, 0);
} else {
//Serial.print("on");
sendCMD(0x10, CMD_SET_INK, 0xD, 0xF, 0x00);
}
sendCMD(0x10, CMD_DRAW_PIXEL, toByte(x*2), toByte(y));
sendCMD(0x10, CMD_DRAW_PIXEL, toByte(x*2+1), toByte(y));
}
}
//sendCMD(0x10, CMD_CLEAR_PAPER);
}
sendCMD(0x10, CMD_SWAP_BUF);
}
Creating the Tempo
The game is controlled by an internal metronone that keeps tack of the beat. This is achieved through a hardware timer "TimeOne.h". Using a timer is the only way to accurately produce a tempo, using millis() would be inaccurate as other parts of the code could hold the program up.
Timer1.initialize(500000); // initialise timer1, and set a 1/2 second period
Once the timer is setup we attach an interrupt that calls a method every time the timer ticks.
Timer1.attachInterrupt(callback); // attaches callback() as a timer overflow interrupt
By doing this we ensure we catch every tick of the timer and therefore maintain a constant beat. This also allows fine grain control over tempo.
In the callback() method we increase the beatCount by one and let the program know that the counters needs to be moved. Callback method should be as simple as possible to not hold up other parts of the code.
void callback()
{
beatCount++;
moveCounters = true;
}
Controlling each Channel
For the moment each channel display dots at a certain freq., simulating a drum kit rhythm.
Each tick of the timer represents a
Semi quaver (or 16th note). = 1 timer tick
Quaver = 8th note = 2 timer ticks
Crotchet = 1/4 note = 4 timer ticks
Minum = 1/2 note = 8 timer ticks
Whole = 1 = 16 timer ticks
The main loop checks to see what the current beat count and checks to see if each channels note duration requires an action.
The counters for each channel are controlled by two methods. One that starts a pulse at the top LED of the column and another than scans each column and moves and pulses down 1 row. Every tick of the timer results in all counters being moved down a row. The frequency is controlled by starting the column at different times.
if (moveCounters) { // if the timer has ticked over
for (int x=0; x<channels; x++){ // for each channel
updateCol(x); // update the current counters
if (beatCount % delays[x] == 0) { //if a new counter is needed
startCol(x); // create a counter at the top of the col
}
}
refreshDisplay();
moveCounters = false; // reset flag for next timer tick
}
void updateCol(int col) {
for (int y=0; y<LEDs; y++) { // for each row in the col
if (displayGrid[col][y]) { // if this LED is on
displayGrid[col][y] = false; // turn it off
if (y>0) { // if this is not the bottom row
displayGrid[col][y-1] = true; // turn on the LED under
}
}
}
}
void startCol(int col) {
displayGrid[col][LEDs-1] = true; // turn on the top LED
}
With a few adjustments to the display method to do some colors you endup with the following...
Using the 8x8 RainbowDuino LED matrix and various vibration sensors and buttons to create a guitar hero style game for playing an electronic drumkit. This is a working example of several research areas including timers, interrupts, sensor readings, I2C bus
Mapping the LED Matrix:
To be able to manipulate the matrix it is represented in code by a 2D array containing on/off values (int 0 or 1)
int displayGrid[LEDs/2][LEDs]; // the LED matrix
initially this is initialised with all values as false. This creates a 4x8 matrix. We are dealing with 4 column as that is reasonable for a drumkit and allows each channel to occupy 2 LED's. This is much easier to view.
From here the Rainbow duino's buffer is written by reading each value of the array and colouring in the matching pixel.
void refreshDisplay() {
sendCMD(0x10, CMD_CLEAR_PAPER);
for(int x=0; x<LEDs/2; x++) {
for (int y=0; y<LEDs; y++) {
if (displayGrid[x][y] == 1) {
if (y==0) {
sendCMD(0x10, CMD_SET_INK, 0xF, 0x0, 0);
} else {
//Serial.print("on");
sendCMD(0x10, CMD_SET_INK, 0xD, 0xF, 0x00);
}
sendCMD(0x10, CMD_DRAW_PIXEL, toByte(x*2), toByte(y));
sendCMD(0x10, CMD_DRAW_PIXEL, toByte(x*2+1), toByte(y));
}
}
//sendCMD(0x10, CMD_CLEAR_PAPER);
}
sendCMD(0x10, CMD_SWAP_BUF);
}
Creating the Tempo
The game is controlled by an internal metronone that keeps tack of the beat. This is achieved through a hardware timer "TimeOne.h". Using a timer is the only way to accurately produce a tempo, using millis() would be inaccurate as other parts of the code could hold the program up.
Timer1.initialize(500000); // initialise timer1, and set a 1/2 second period
Once the timer is setup we attach an interrupt that calls a method every time the timer ticks.
Timer1.attachInterrupt(callback); // attaches callback() as a timer overflow interrupt
By doing this we ensure we catch every tick of the timer and therefore maintain a constant beat. This also allows fine grain control over tempo.
In the callback() method we increase the beatCount by one and let the program know that the counters needs to be moved. Callback method should be as simple as possible to not hold up other parts of the code.
void callback()
{
beatCount++;
moveCounters = true;
}
Controlling each Channel
For the moment each channel display dots at a certain freq., simulating a drum kit rhythm.
Each tick of the timer represents a
Semi quaver (or 16th note). = 1 timer tick
Quaver = 8th note = 2 timer ticks
Crotchet = 1/4 note = 4 timer ticks
Minum = 1/2 note = 8 timer ticks
Whole = 1 = 16 timer ticks
The main loop checks to see what the current beat count and checks to see if each channels note duration requires an action.
The counters for each channel are controlled by two methods. One that starts a pulse at the top LED of the column and another than scans each column and moves and pulses down 1 row. Every tick of the timer results in all counters being moved down a row. The frequency is controlled by starting the column at different times.
if (moveCounters) { // if the timer has ticked over
for (int x=0; x<channels; x++){ // for each channel
updateCol(x); // update the current counters
if (beatCount % delays[x] == 0) { //if a new counter is needed
startCol(x); // create a counter at the top of the col
}
}
refreshDisplay();
moveCounters = false; // reset flag for next timer tick
}
void updateCol(int col) {
for (int y=0; y<LEDs; y++) { // for each row in the col
if (displayGrid[col][y]) { // if this LED is on
displayGrid[col][y] = false; // turn it off
if (y>0) { // if this is not the bottom row
displayGrid[col][y-1] = true; // turn on the LED under
}
}
}
}
void startCol(int col) {
displayGrid[col][LEDs-1] = true; // turn on the top LED
}
With a few adjustments to the display method to do some colors you endup with the following...
Mic Based Interrupts
Problem:
We need to be able to wake the Arduino when the koala starts calling. The initial test is a volume test. We want the Arduino to wake up when the mic registers a load noise.
To wake the Arduino from sleep an interrupt must be generated and sent to pin 2 or 3.
Although the attach interrupt method allows various parameters to be set for the interrupt (high, low, faling etc) in any mode except "Idle" only Low can be used.
This poses a problem as we need to generate a low signal when the mic reading reaches a certain threshold. Also, as LOW is defined as 0-2V the ambient reading from the mic is actually a LOW signal and it continually wakes the Arduino. Pulling the PIN high with a pull-up resistor pulls it high, but only while nothing is connected. Once the sensor is plugged back in the pin reads normal values. Though in some cases (sound sensor) the pull-up resistor affected the reading.
Solution:
Using and operational pre-amp to set a threshold voltage and generate a low signal when the signal crosses the threshold. An Operational Amplifier (op-amp) is designed to amplify the difference between the V+ and V- input pins. Since the amplifier has very high gain, in this configuration its output is either fully on, or fully off, depending on whether the input (in this case on V-) is greater than, or less than, the reference voltage we put on V+. I am using the LM358
LM358
PIN1 to Arduino interrupt pin. This output will snap low when the input exceeds the reference voltage.
PIN 2 to Mic Signal Output. This is the signal we will monitor
PIN3 to POT signal Out. The pot provides an adjustable reference voltage.
PIN 4 to ground
PIN 5, 6, 7 provide a second input
PIN 8 5V Power supply
Both Mic and Pot are connected through pull-up resistors. Once a threshold V has been calculated we might replace the pot with resistors and hard wire it in.
We need to be able to wake the Arduino when the koala starts calling. The initial test is a volume test. We want the Arduino to wake up when the mic registers a load noise.
To wake the Arduino from sleep an interrupt must be generated and sent to pin 2 or 3.
Although the attach interrupt method allows various parameters to be set for the interrupt (high, low, faling etc) in any mode except "Idle" only Low can be used.
This poses a problem as we need to generate a low signal when the mic reading reaches a certain threshold. Also, as LOW is defined as 0-2V the ambient reading from the mic is actually a LOW signal and it continually wakes the Arduino. Pulling the PIN high with a pull-up resistor pulls it high, but only while nothing is connected. Once the sensor is plugged back in the pin reads normal values. Though in some cases (sound sensor) the pull-up resistor affected the reading.
Solution:
Using and operational pre-amp to set a threshold voltage and generate a low signal when the signal crosses the threshold. An Operational Amplifier (op-amp) is designed to amplify the difference between the V+ and V- input pins. Since the amplifier has very high gain, in this configuration its output is either fully on, or fully off, depending on whether the input (in this case on V-) is greater than, or less than, the reference voltage we put on V+. I am using the LM358
PIN1 to Arduino interrupt pin. This output will snap low when the input exceeds the reference voltage.
PIN 2 to Mic Signal Output. This is the signal we will monitor
PIN3 to POT signal Out. The pot provides an adjustable reference voltage.
PIN 4 to ground
PIN 5, 6, 7 provide a second input
PIN 8 5V Power supply
Both Mic and Pot are connected through pull-up resistors. Once a threshold V has been calculated we might replace the pot with resistors and hard wire it in.
RainbowDuino
The Rainbowduino board is an Arduino compatible controller board with professional LED driving capacity. It will drive an 8x8 RGB Led Matrix.
Tech Specs:
- No external circuit required, plug and shine!
- 24 constant current channels of 120mA each
- 8 super source driver channel of 500mA each
- Wide output voltage adaption from 6.5V-12VDC (runs off Arduino 5V)
- Dedicated GPIO and ADC
- Hardware UART and I2C communication
To get the Rainbowduino up and running some firmware needs to be uploaded to the device.
There are several versions available however the simplest seems to be "RainbowDuino Firmware Version 3"
http://code.google.com/p/rainbowduino-firmware/
Uploading the Firmware:
There are several ways to go about doing this - via Arduino, via Xbee USB. Via Arduino seems the simplest.
Step 1:
Make sure that your Arduino has no software running. To erase any code, flash the processor with the following void code:
void setup(){ } void loop(){ }
Step 2:
Connections:
Flashing the firmware requires the serial connection so connect:
Rainbowduino -- > Arduino
RX 0/RX
TX 1/TX
DTR Reset
VCC 5V
GND GND
Step 3:
Open the rainbowduino firmware sketch, select the serial port and the correct board and upload the sketch.
In this case, even though I am using and Arduino Uno I select Atmega 168 As the board.
The rainbowduino should enter its default rainbow state if the upload was successful.
Step 4:
Add the Rainbowduino v3 firmware master folder to the Arduino libraries. This should containt WireCommnad.h and WireCommuncation.h
Controlling the RainbowDuino
Achieved through IC2 protocol.
A4 to SDA
A5 to SCL
RST to DTR
VCC to 5V
GND to GND
Reference LIbrary
http://code.google.com/p/rainbowduino-firmware/wiki/FirmwareFunctionsReference
Initialising the RainbowDuino
// variables for sending data
unsigned char RainbowCMD[20]; // buffer for storing the commands as they are send byte by byte
unsigned char State = 0; // determines send or receive command 0 for send
unsigned long timeout; // transfer timeout value
byte remoteAddr = 0x10; // bus address of rainbowduino
setup() {
Wire.begin();
RainbowCMD[0] = 'r';
sendCMD(0x10, CMD_SET_PAPER, 0, 0, 0);
}
void sendCommand() //wrapper
void sendWireCommand() // send across the I2C bus
void toByte() // int to byte convertor
Drawing Images
The device has two buffers that are swapped. You sends display message to a buffer and call the swap buffer method to display the new image. This allows multiple draw command to be send to construct the image and set the color ect before displaying it.
//sendCMD(0x10, CMD_CLEAR_PAPER);
sendCMD(0x10, CMD_SET_INK, 0xD, 0xF, 0x00);
sendCMD(0x10, CMD_DRAW_PIXEL, toByte(x), toByte(y));
sendCMD(0x10, CMD_SWAP_BUF);
Sunday, 18 September 2011
Mic Audio Sampling
Using the Audio sound sensor to try to capture audio data.
The Code:
Setup:
startTime = millis();
Main Loop
if(!end) {
sensorRead = analogRead(A0);
file.println(sensorRead); // write the read to the file
}
// stop the recording after 10 seconds
if (millis() > 10000) {
endTime = millis();
duration = endTime - startTime;
file.println(duration);
file.close(); //Close the file.
end=true;
Serial.print("done");
while(1);
}
Each sensor reading is written to the text file. This loop records makes note of the start and end time to calculate the length (for future designs when length will not be set at 10 secs). The duration is added as the final value.
Results
After running the output file through he wave convertor I ended up wit the following file. The recording is me saying "hello" into the mic over and over. The wave convertor calculates the sample rate at 3289sample/sec. I need to investigate other sensors and see if a raw write will allow faster sampling.
The Code:
Setup:
startTime = millis();
Main Loop
if(!end) {
sensorRead = analogRead(A0);
file.println(sensorRead); // write the read to the file
}
// stop the recording after 10 seconds
if (millis() > 10000) {
endTime = millis();
duration = endTime - startTime;
file.println(duration);
file.close(); //Close the file.
end=true;
Serial.print("done");
while(1);
}
Each sensor reading is written to the text file. This loop records makes note of the start and end time to calculate the length (for future designs when length will not be set at 10 secs). The duration is added as the final value.
Results
After running the output file through he wave convertor I ended up wit the following file. The recording is me saying "hello" into the mic over and over. The wave convertor calculates the sample rate at 3289sample/sec. I need to investigate other sensors and see if a raw write will allow faster sampling.
Java Wave Creator
After failing to create an audio header on the arduino I found a Java library that writes audio data to a wav file. This actually works out better as more of the processing is shifted off the arduino.
Java Wave File I/O
The
This class reads the data from a text file created by the Arduino. As this is 8 bit recording all values are between 0-255. I need to investigate if scaling will be required to normalise the sound.
Creating the Wave file:
Before creating the wave file the length and duration of the recording must be calculated.
This is achieved by opening the text file, couting each sample and storign the last value, as it is the duration in miliseconds.
This method creates a new wav file for writing to. It writes header information to the wav file. Once this method has been called and a
file : File output = new File(output.wav"); // specifies the output file
Again the data text file is opened however this time each value is added to the wav file
The last value is excluded as it is the length.
Java Wave File I/O
The
WavFile class takes care of all the file IO for reading and writing audio data to and from wave files. The WavFile class provides methods for accessing wav file data using different java primitive types, these are long, int and double. It is up to the user to choose the correct type for the given wav file's sample resolution.This class reads the data from a text file created by the Arduino. As this is 8 bit recording all values are between 0-255. I need to investigate if scaling will be required to normalise the sound.
Creating the Wave file:
Before creating the wave file the length and duration of the recording must be calculated.
This is achieved by opening the text file, couting each sample and storign the last value, as it is the duration in miliseconds.
BufferedReader br = new BufferedReader(new FileReader(fileName));
String strLine;
//Read File Line By Line
while ((strLine = br.readLine()) != null) {
numSamples++;
duration = Double.parseDouble(strLine)/1000;
}
numSamples--; // compstate for the legnth on the end
//Close the input stream
br.close();
Also, the sample rate must be calculated before preccding. sampleRate = (int) (numSamples / duration);
Now the wave file can be instantiated.
newWavFile(File file, int numChannels, long numFrames, int validBits, long sampleRate) throws IOException, WavFileExceptionThis method creates a new wav file for writing to. It writes header information to the wav file. Once this method has been called and a
WavFile instance retrieved, thewriteFrames methods can be used to write samples to the wav file.file : File output = new File(output.wav"); // specifies the output file
Again the data text file is opened however this time each value is added to the wav file
The last value is excluded as it is the length.
// Create a wav file with the name specified as the first argument
WavFile wavFile = WavFile.newWavFile(outfile, numChannels, numSamples, bitRate, sampleRate);
BufferedReader br = new BufferedReader(new FileReader(fileName));
String strLine;
//Read File Line By Line
int i = 0;
while (i <= numSamples) {
strLine = br.readLine();
i++;
//System.out.print(strLine);
int[] value = {Integer.parseInt(strLine)};
wavFile.writeFrames(value, 1);
}
//Close the input stream
br.close();
wavFile.close();
Tuesday, 6 September 2011
Midi Motion Sensors
Aim: Using the IR sensor to create a thermion like instrument
Connections:
To create the midi interface connect the Arduino to a 5pin Din plug as shown. A 220ohm resistor is needed to lower the 5V power.

Code:
From my tests I know the IR sensor reads values from approx. 0 - 600. To create the simple instrument i mapped each of these sensor readings to a MIDI note value (0-127).
The Midi Library provides a simple way to send midi signals.
Once MIDI.begin() is send the serial port is used for MIDI and cannot print message to the serial reader. Midi uses a customer Baud rate (31250).
Results:
Connections:
To create the midi interface connect the Arduino to a 5pin Din plug as shown. A 220ohm resistor is needed to lower the 5V power.
Code:
From my tests I know the IR sensor reads values from approx. 0 - 600. To create the simple instrument i mapped each of these sensor readings to a MIDI note value (0-127).
The Midi Library provides a simple way to send midi signals.
| void | begin (const byte inChannel=1) |
| Call the begin method in the setup() function of the Arduino. | |
| void | sendNoteOn (byte NoteNumber, byte Velocity, byte Channel) |
| Send a Note On message. | |
| void | sendNoteOff (byte NoteNumber, byte Velocity, byte Channel) |
| Send a Note Off message (a real Note Off, not a Note On with null velocity) |
| void | sendProgramChange (byte ProgramNumber, byte Channel) |
| Send a Program Change message. |
Once MIDI.begin() is send the serial port is used for MIDI and cannot print message to the serial reader. Midi uses a customer Baud rate (31250).
Results:
Monday, 5 September 2011
Sensor Capabilities
Potentiometer
Read Protocol: Analogue
Values: 0-1023
Voltage: 5V
Mic
Read Protocol: Unknown - needs further investigation
Values: unknown
Voltage: 2.2V - 7V
Ultrasonic Distance Sensor
Read Protocol: Digital
Values: 0-1
Voltage: 5V
This sensor uses a ping operation to calculate distance. The connected pin is first set to out and a short burst is emitted. The pin is then switch back to input to read the reflected signal.
To connect this to the shield ensure that the plug is connect slip side up. See the picture below
IR Distance Sensor
Read Protocol: Analogue
Values: 0-600
Voltage: 5V
Originally the connector cable was wired incorrectly so i had to swap to signal pin around. Of note the sensor reaches it max value about 10cm away from the LEDs. Once you move close than this the value decreases till about 500 when your hand is over the sensor.
Read Protocol: Analogue
Values: 0-1023
Voltage: 5V
Mic
Read Protocol: Unknown - needs further investigation
Values: unknown
Voltage: 2.2V - 7V
Ultrasonic Distance Sensor
Read Protocol: Digital
Values: 0-1
Voltage: 5V
This sensor uses a ping operation to calculate distance. The connected pin is first set to out and a short burst is emitted. The pin is then switch back to input to read the reflected signal.
To connect this to the shield ensure that the plug is connect slip side up. See the picture below
IR Distance Sensor
Read Protocol: Analogue
Values: 0-600
Voltage: 5V
Originally the connector cable was wired incorrectly so i had to swap to signal pin around. Of note the sensor reaches it max value about 10cm away from the LEDs. Once you move close than this the value decreases till about 500 when your hand is over the sensor.
Temperature Sensor
Read Protocol: Analogue
Values: 0-?
Voltage: 5V ok
Room temp: about 512
Upon touch the temp instantly rises to about 540 and steadily increases.
Read Protocol: Analogue
Values: 0-?
Voltage: 5V ok
Room temp: about 512
Upon touch the temp instantly rises to about 540 and steadily increases.
ADKeyboard Module
Read Protocol: Analogue
Values: 50, 200, 400, 600, 800 (serial readings more like 0, 143, 329, 504, 742)
Voltage: 5V
Read Protocol: Analogue
Values: 50, 200, 400, 600, 800 (serial readings more like 0, 143, 329, 504, 742)
Voltage: 5V
PS2 Control Module
Read Protocol: Analogue
Values: 0-1023 x 2 plus button
Voltage: 5V
Although this unit has two ground pins both x and y values could be read connecting either. Still need to investigate the button.
Read Protocol: Analogue
Values: 0-1023 x 2 plus button
Voltage: 5V
Although this unit has two ground pins both x and y values could be read connecting either. Still need to investigate the button.
Subscribe to:
Posts (Atom)
