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...
Addition of sensor reading:
ReplyDeleteif (moveCounters) {
for (int x=0; x<channels; x++){
updateCol(x);
if (beatCount % delays[x] == 0) {
startCol(x);
}
}
refreshDisplay();
enableSensors();
moveCounters = false;
}
checkSensors();
Every time the counters are moved (every tick of the timer) the appropriate sensors are switched on by enableSensors().
Constantly running in the background is checkSensors which checks the input of any sensors currently "on".
void enableSensors() {
for(int x=0; x<LEDs/2; x++) {
if (displayGrid[x][0]) {
shouldCheck[x] = true;
} else {
shouldCheck[x] = false;
}
}
}
void checkSensors() {
for (int i=0; i<channels; i++) {
if (shouldCheck[i]) {
if (getInput(i)) {
Serial.print("hit");
Serial.println(i);
//playNote(i);
shouldCheck[i] = false;
missed[i] = false;
}
else {
missed[i] = true;
//Serial.println("false");
}
}
}
}