Skip to content

Instantly share code, notes, and snippets.

@davidcool
Last active December 10, 2015 13:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save davidcool/4439033 to your computer and use it in GitHub Desktop.
Save davidcool/4439033 to your computer and use it in GitHub Desktop.
This sketch (unsuccessfully) combines code from three different places: 1) http://www.instructables.com/id/Use-an-Accelerometer-and-Gyroscope-with-Arduino/ 2) http://www.i2cdevlib.com/devices/mpu6050#source 3) https://www.sparkfun.com/products/10628 The idea is to run the single pixel test sketch (at bottom of page) from the instructables site w…
//single pixel sound sketch
//by David Cool 2012
//http://davidcool.com/
//This is a merge of the two programs credited below to work with the MPU6050 IMU
//accelerometer test- single pixel
//by Amanda Ghassaei 2012
//http://www.instructables.com/id/Use-an-Accelerometer-and-Gyroscope-with-Arduino/
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
*/
// I2C device class (I2Cdev) demonstration Arduino sketch for MPU6050 class
// 10/7/2011 by Jeff Rowberg <jeff@rowberg.net>
// Updates should (hopefully) always be available at https://github.com/jrowberg/i2cdevlib
//
// Changelog:
// 2011-10-07 - initial release
/* ============================================
I2Cdev device library code is placed under the MIT license
Copyright (c) 2011 Jeff Rowberg
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
===============================================
*/
/*
*
* File: MP3_Shield_RealtimeMIDI.ino
* Author: Matthias Neeracher
*
* This code is in the public domain, with the exception of the contents of sVS1053b_Realtime_MIDI_Plugin.
*
* The code is based on Nathan Seidle's Sparkfun Electronics example code for the Sparkfun
* MP3 Player and Music Instrument shields and and VS1053 breakout board.
*
* http://www.sparkfun.com/Code/MIDI_Example.pde
* http://dlnmh9ip6v2uc.cloudfront.net/datasheets/Dev/Arduino/Shields/VS_Shield_Example.zip
*
* Spark Fun Electronics 2011
* Nathan Seidle
*
* This code is public domain but you buy me a beer if you use this and we meet someday (Beerware license).
*
* THEORY OF OPERATIONS
*
* The VS1053b has two ways of playing MIDI: One method is that you simply send a Standard MIDI level 0 file through
* SPI, and the chip will play it. This works exactly the same way as MP3 mode and will not be discussed further here.
* The other method is that the VS1053b has a "Real Time MIDI mode", in which it will instantly execute MIDI commands
* sent to it through either the UART or SPI.
*
* Real Time MIDI mode can be enabled with two different methods, controlled by USE_GPIO_INIT
* (1) Setting GPIO1 to HIGH (which is hardwired in the Sparkfun Music Instrument shield, and can be done through
* pin 4 in the MP3 Player Shield)
* (0) Sending a small software patch through SPI.
*
* MIDI data can be sent with two different methods as well, controlled by USE_SPI_MIDI
* (0) Through a (software) serial connection on pin 3, at 31250 baud
* (1) Through SPI, at an arbitrary data rate. For SPI, each byte of MIDI data needs to be prefixed by a 0 byte
* (The V1053b data sheet erroneously states that the padding should be a 0xFF byte).
*
* Both initialization methods and both transmission methods can be selected through the #defines below. Out of the box,
* it probably makes most sense to enable real time MIDI through pin 4, and send serial data through pin 3, but if you
* want to cut the traces for pin 3 and 4 and use those pins for another purpose, the alternative methods may come in
* handy.
*/
#define USE_GPIO_INIT 1
#define USE_SPI_MIDI 0
#define USE_PATCH_INIT !USE_GPIO_INIT
#define USE_SERIAL_MIDI !USE_SPI_INIT
#define USE_SPI (USE_SPI_MIDI||USE_PATCH_INIT)
#if USE_SPI
#include <SPI.h>
#endif
#if USE_SERIAL_MIDI
#include <SoftwareSerial.h>
SoftwareSerial midiSerial(2,3); // Soft TX on 3, RX not used (2 is an input anyway, for VS_DREQ)
#endif
#if USE_SPI
#define VS_XCS 6 // Control Chip Select Pin (for accessing SPI Control/Status registers)
#define VS_XDCS 7 // Data Chip Select / BSYNC Pin
#define VS_DREQ 2 // Data Request Pin: Player asks for more data
#endif
#if USE_GPIO_INIT
#define VS_GPIO1 4 // Mode selection (0 = file / 1 = real time MIDI)
#endif
#define VS_RESET 8 //Reset is active low
#if USE_PATCH_INIT
//Write to VS10xx register
//SCI: Data transfers are always 16bit. When a new SCI operation comes in
//DREQ goes low. We then have to wait for DREQ to go high again.
//XCS should be low for the full duration of operation.
void VSWriteRegister(unsigned char addressbyte, unsigned char highbyte, unsigned char lowbyte){
while(!digitalRead(VS_DREQ)) ; //Wait for DREQ to go high indicating IC is available
digitalWrite(VS_XCS, LOW); //Select control
//SCI consists of instruction byte, address byte, and 16-bit data word.
SPI.transfer(0x02); //Write instruction
SPI.transfer(addressbyte);
SPI.transfer(highbyte);
SPI.transfer(lowbyte);
while(!digitalRead(VS_DREQ)) ; //Wait for DREQ to go high indicating command is complete
digitalWrite(VS_XCS, HIGH); //Deselect Control
}
//
// Plugin to put VS10XX into realtime MIDI mode
// Originally from http://www.vlsi.fi/fileadmin/software/VS10XX/vs1053b-rtmidistart.zip
// Permission to reproduce here granted by VLSI solution.
//
const unsigned short sVS1053b_Realtime_MIDI_Plugin[28] = { /* Compressed plugin */
0x0007, 0x0001, 0x8050, 0x0006, 0x0014, 0x0030, 0x0715, 0xb080, /* 0 */
0x3400, 0x0007, 0x9255, 0x3d00, 0x0024, 0x0030, 0x0295, 0x6890, /* 8 */
0x3400, 0x0030, 0x0495, 0x3d00, 0x0024, 0x2908, 0x4d40, 0x0030, /* 10 */
0x0200, 0x000a, 0x0001, 0x0050,
};
void VSLoadUserCode(void) {
int i = 0;
while (i<sizeof(sVS1053b_Realtime_MIDI_Plugin)/sizeof(sVS1053b_Realtime_MIDI_Plugin[0])) {
unsigned short addr, n, val;
addr = sVS1053b_Realtime_MIDI_Plugin[i++];
n = sVS1053b_Realtime_MIDI_Plugin[i++];
while (n--) {
val = sVS1053b_Realtime_MIDI_Plugin[i++];
VSWriteRegister(addr, val >> 8, val & 0xFF);
}
}
}
#endif
// merge
// Arduino Wire library is required if I2Cdev I2CDEV_ARDUINO_WIRE implementation
// is used in I2Cdev.h
#include "Wire.h"
// I2Cdev and MPU6050 must be installed as libraries, or else the .cpp/.h files
// for both classes must be in the include path of your project
#include "I2Cdev.h"
#include "MPU6050.h"
// class default I2C address is 0x68
// specific I2C addresses may be passed as a parameter here
// AD0 low = 0x68 (default for InvenSense evaluation board)
// AD0 high = 0x69
MPU6050 accelgyro;
int16_t ax, ay, az;
int16_t gx, gy, gz;
//pin connections
#define ledLatchPin 43
#define ledClockPin 44
#define ledDataPin 42
#define buttonLatchPin 45
#define buttonClockPin 46
#define buttonDataPin 47
//setup varibles for Gyroscope/Accelerometer
int xGyroRAW;
int yGyroRAW;
int xAccRAW;
int yAccRAW;
int zAccRAW;
byte xGyro;
byte yGyro;
byte xAcc;
byte yAcc;
byte zAcc;
//looping variables
byte i;
byte j;
byte k;
//storage for led states, 4 bytes
byte ledData[] = {0, 0, 0, 0};
//storage for buttons, 4 bytes
byte buttonCurrent[] = {0,0,0,0};
byte buttonLast[] = {0,0,0,0};
byte buttonEvent[] = {0,0,0,0};
byte buttonState[] = {0,0,0,0};
//button debounce counter- 16 bytes
byte buttonDebounceCounter[4][4];
//variables for accelerometer pixel movement
boolean firstPress = 1;
byte movingPixel[] = {0, 0, 0, 0};
byte yPosition;
byte xPosition;
int timeX = 0;
int timeY = 0;
boolean dirX = 0;
boolean dirY = 0;
byte lastX = 4;
byte lastY = 4;
//MIDI variables
int velocity = 100;
int noteON = 144;
int MIDIoffset = 60;
byte currentX;
// end merge
void setup() {
#if USE_SPI
pinMode(VS_DREQ, INPUT);
pinMode(VS_XCS, OUTPUT);
pinMode(VS_XDCS, OUTPUT);
digitalWrite(VS_XCS, HIGH); //Deselect Control
digitalWrite(VS_XDCS, HIGH); //Deselect Data
#endif
#if USE_SERIAL_MIDI
midiSerial.begin(31250);
#endif
pinMode(VS_RESET, OUTPUT);
Serial.begin(57600); //Use serial for debugging
Serial.println("\n******\n");
Serial.println("MP3 Shield Example");
//Initialize VS1053 chip
digitalWrite(VS_RESET, LOW); //Put VS1053 into hardware reset
#if USE_SPI
//Setup SPI for VS1053
pinMode(53, OUTPUT); //Pin 10 must be set as an output for the SPI communication to work
SPI.begin();
SPI.setBitOrder(MSBFIRST);
SPI.setDataMode(SPI_MODE0);
//From page 12 of datasheet, max SCI reads are CLKI/7. Input clock is 12.288MHz.
//Internal clock multiplier is 1.0x after power up.
//Therefore, max SPI speed is 1.75MHz. We will use 1MHz to be safe.
SPI.setClockDivider(SPI_CLOCK_DIV16); //Set SPI bus speed to 1MHz (16MHz / 16 = 1MHz)
SPI.transfer(0xFF); //Throw a dummy byte at the bus
#endif
delayMicroseconds(1);
digitalWrite(VS_RESET, HIGH); //Bring up VS1053
#if USE_PATCH_INIT
VSLoadUserCode();
#else
pinMode(VS_GPIO1, OUTPUT);
digitalWrite(VS_GPIO1, HIGH); // Enable real time MIDI mode
#endif
// merge
// join I2C bus (I2Cdev library doesn't do this automatically)
Wire.begin();
// initialize device
//Serial.println("Initializing I2C devices...");
accelgyro.initialize();
// verify connection
Serial.println("Testing device connections...");
Serial.println(accelgyro.testConnection() ? "MPU6050 connection successful" : "MPU6050 connection failed");
// Amanda's setup
DDRL = 0xFA;//set pins D45-D42 as output, D47 as input
cli();//stop interrupts
//set timer1 interrupt at 1kHz
TCCR1A = 0;// set entire TCCR1A register to 0
TCCR1B = 0;// same for TCCR1B
TCNT1 = 0;//initialize counter value to 0;
// set timer count for 1khz increments
OCR1A = 1999;// = (16*10^6) / (1000*8) - 1
// turn on CTC mode
TCCR1B |= (1 << WGM12);
// Set CS11 bit for 8 prescaler
TCCR1B |= (1 << CS11);
// enable timer compare interrupt
TIMSK1 |= (1 << OCIE1A);
sei();//allow interrupts
// end merge
} // end setup
void sendMIDI(byte data)
{
#if USE_SPI_MIDI
SPI.transfer(0);
SPI.transfer(data);
#else
midiSerial.write(data);
#endif
}
//Plays a MIDI note. Doesn't check to see that cmd is greater than 127, or that data values are less than 127
void talkMIDI(byte cmd, byte data1, byte data2) {
#if USE_SPI_MIDI
//
// Wait for chip to be ready (Unlikely to be an issue with real time MIDI)
//
while (!digitalRead(VS_DREQ))
;
digitalWrite(VS_XDCS, LOW);
#endif
sendMIDI(cmd);
//Some commands only have one data byte. All cmds less than 0xBn have 2 data bytes
//(sort of: http://253.ccarh.org/handout/midiprotocol/)
if( (cmd & 0xF0) <= 0xB0 || (cmd & 0xF0) >= 0xE0) {
sendMIDI(data1);
sendMIDI(data2);
} else {
sendMIDI(data1);
}
#if USE_SPI_MIDI
digitalWrite(VS_XDCS, HIGH);
#endif
}
//Send a MIDI note-on message. Like pressing a piano key
//channel ranges from 0-15
void noteOn(byte channel, byte note, byte attack_velocity) {
talkMIDI( (0x90 | channel), note, attack_velocity);
}
//Send a MIDI note-off message. Like releasing a piano key
void noteOff(byte channel, byte note, byte release_velocity) {
talkMIDI( (0x80 | channel), note, release_velocity);
}
// merge
ISR(TIMER1_COMPA_vect) {//Interrupt at freq of 1kHz
timeX++;//increment timeX
timeY++;//increment timeY
shift();//send data to leds
}
// buttonCheck - checks the state of a given button.
//this buttoncheck function is largely copied from the monome 40h firmware by brian crabtree and joe lake
void buttonCheck(byte row, byte index)
{
if (((buttonCurrent[row] ^ buttonLast[row]) & (1 << index)) && // if the current physical button state is different from the
((buttonCurrent[row] ^ buttonState[row]) & (1 << index))) { // last physical button state AND the current debounced state
if (buttonCurrent[row] & (1 << index)) { // if the current physical button state is depressed
buttonEvent[row] = 1 << index; // queue up a new button event immediately
buttonState[row] |= (1 << index); // and set the debounced state to down.
}
else{
buttonDebounceCounter[row][index] = 12;
} // otherwise the button was previously depressed and now
// has been released so we set our debounce counter.
}
else if (((buttonCurrent[row] ^ buttonLast[row]) & (1 << index)) == 0 && // if the current physical button state is the same as
(buttonCurrent[row] ^ buttonState[row]) & (1 << index)) { // the last physical button state but the current physical
// button state is different from the current debounce
// state...
if (buttonDebounceCounter[row][index] > 0 && --buttonDebounceCounter[row][index] == 0) { // if the the debounce counter has
// been decremented to 0 (meaning the
// the button has been up for
// kButtonUpDefaultDebounceCount
// iterations///
buttonEvent[row] = 1 << index; // queue up a button state change event
if (buttonCurrent[row] & (1 << index)){ // and toggle the buttons debounce state.
buttonState[row] |= (1 << index);
}
else{
buttonState[row] &= ~(1 << index);
}
}
}
}
void shift(){
for (i=0;i<4;i++){
buttonLast[i] = buttonCurrent[i];
byte dataToSend = (1 << (i+4)) | (15 & ~ledData[i]);
// set latch pin low so the LEDs don't change while sending in bits
PORTL&=B10111111;//digitalWrite(ledLatchPin, LOW);
// shift out the bits of dataToSend
//shiftOut(ledDataPin, ledClockPin, LSBFIRST, dataToSend);
for (j=0;j<8;j++){
PORTL&=B11011111;//digitalWrite(ledClockPin,LOW);
//digitalWrite(ledDataPin,((dataToSend>>j)&1));
if ((dataToSend>>j)&1){
PORTL|=B10000000;
}
else{
PORTL&=B01111111;
}
PORTL|=B00100000;//digitalWrite(ledClockPin,HIGH);
}
//set latch pin high so the LEDs will receive new data
PORTL|=B01000000;//digitalWrite(ledLatchPin, HIGH);
// SlowDown is put in here to waste a little time while we wait for the state of the output
// pins to settle. Without this time wasting loop, a single button press would show up as
// two presses (the button and its neighbour)
volatile int SlowDown = 0;
while (SlowDown < 15)
{
SlowDown++;
}
//once one row has been set high, receive data from buttons
//set latch pin high
PORTL|=B00010000;//digitalWrite(buttonLatchPin, HIGH);
//shift in data
//buttonCurrent[i] = shiftIn(buttonDataPin, buttonClockPin, LSBFIRST) >> 3;
for (j=0;j<4;j++){
PORTL&=B11110111;//digitalWrite(buttonClockPin,LOW);
PORTL|=B00001000;//digitalWrite(buttonClockPin,HIGH);
}
for (j=0;j<4;j++){
PORTL&=B11110111;//digitalWrite(buttonClockPin,LOW);
if ((PIND>>2)&1){//digitalRead(buttonDataPin)
buttonCurrent[i]|=1<<j;
}
else{
buttonCurrent[i]&=~(1<<j);
}
PORTL|=B00001000;//digitalWrite(buttonClockPin,HIGH);
}
//latchpin low
PORTL&=B11101111;//digitalWrite(buttonLatchPin, LOW);
for (k=0;k<4;k++){
buttonCheck(i,k);
}
}
//turn off leds- this way one row does not appear brighter than the rest
// set latch pin low so the LEDs don't change while sending in bits
PORTL&=B10111111;//digitalWrite(ledLatchPin, LOW);
// shift out 0
//shiftOut(ledDataPin, ledClockPin, LSBFIRST, 0);
for (j=0;j<8;j++){
PORTL&=B11011111;//digitalWrite(ledClockPin,LOW);
PORTL&=B01111111;
PORTL|=B00100000;//digitalWrite(ledClockPin,HIGH);
}
//set latch pin high so the LEDs will receive new data
PORTL|=B01000000;//digitalWrite(ledLatchPin, HIGH);
}
void checkFirstButton(){
for (byte a=0;a<4;a++){
if (buttonEvent[a]){
for (byte b=0;b<4;b++){
if (buttonState[a]&(1<<b)){
//toggle firstPress variable
firstPress = 0;
//display pressed pixel
ledData[a] = buttonEvent[a];
//store current position
yPosition = a;
xPosition = 1<<b;
//reset timers
timeX = 0;
timeY = 0;
return;
}
}
}
}
}
byte scaleAcc(int RAW){
if (RAW<=10000 && RAW>=-10000){
return 5;
}
else if (RAW<-10000){
if (RAW<-50000){
return 0;
}
else if (RAW<-40000){
return 1;
}
else if (RAW<-30000){
return 2;
}
else if (RAW<-20000){
return 3;
}
else{
return 4;
}
}
else if (RAW>10000){
if (RAW>50000){
return 10;
}
else if (RAW>40000){
return 9;
}
else if (RAW>30000){
return 8;
}
else if (RAW>20000){
return 7;
}
else{
return 6;
}
}
}
void checkAccelerometer(){
// read raw accel/gyro measurements from device
accelgyro.getMotion6(&ax, &ay, &az, &gx, &gy, &gz);
/*
Serial.print("ax: ");
Serial.print(ax);
Serial.print("ay: ");
Serial.print(ay);
Serial.print("az: ");
Serial.print(az);
Serial.print("gx: ");
Serial.print(gx);
Serial.print("gy: ");
Serial.print(gy);
Serial.print("gz: ");
Serial.println(gz);
*/
// these methods (and a few others) are also available
//accelgyro.getAcceleration(&ax, &ay, &az);
//accelgyro.getRotation(&gx, &gy, &gz);
//for now just use raw data to see what happens
xGyroRAW = gx;
yGyroRAW = gy;
xAccRAW = ax;
yAccRAW = ay;
zAccRAW = az;
if (xAccRAW>0){
dirX = 1;
}
else{
dirX = 0;
}
if (yAccRAW>0){
dirY = 1;
}
else{
dirY = 0;
}
//convert to 0-10
xAcc = scaleAcc(xAccRAW);
yAcc = scaleAcc(yAccRAW);
}
int getTime(byte acceleration){
switch (acceleration){
case 0://max - acceleration
return 25;
break;
case 1:
return 25;
break;
case 2:
return 50;
break;
case 3:
return 100;
break;
case 4:
return 150;
break;
case 5://lying flat
return 0;
break;
case 6:
return 150;
break;
case 7:
return 100;
break;
case 8:
return 50;
break;
case 9:
return 25;
break;
case 10://max + acceleration
return 25;
break;
}
}
void moveXPixel(int timeComp){
if (timeComp==0){
}
else{
if (timeX>timeComp){
timeX = 0;
if (dirX){
if (xPosition==8){
}
else{
xPosition = xPosition<<1;
}
}
else{
if (xPosition==1){
}
else{
xPosition = xPosition>>1;
}
}
}
}
}
void moveYPixel(int timeComp){
if (timeComp==0){
}
else{
if (timeY>timeComp){
timeY = 0;
if (dirY){
if (yPosition==7){ // changed to 7 to reference 4x8 board (was set at 3)
}
else{
yPosition = yPosition+=1;
}
}
else{
if (yPosition==0){
}
else{
yPosition = yPosition-=1;
}
}
}
}
}
void checkMIDI(){
//convert xPosition to decimal
switch (xPosition){
case 1:
currentX = 0;
break;
case 2:
currentX = 1;
break;
case 4:
currentX = 2;
break;
case 8:
currentX = 3;
break;
}
//if pixel has moved send midi
if (lastX != currentX || lastY != yPosition){
talkMIDI(0xB0, 0x07, 120); //0xB0 is channel message, set channel volume to near max (127)
#if 1
talkMIDI(0xB0, 0, 0x00); //Default bank GM1
talkMIDI(0xC0, 13, 0); //Set instrument number, 13 = marimba here. 0xC0 is a 1 data byte command
noteOff(0, (lastX+5*lastY+MIDIoffset), 0);
noteOn(0, (currentX+5*yPosition+MIDIoffset), 127);
#endif
#if 0
talkMIDI(0xB0, 0, 0 ? 0x79 : 0); // third argument controls midi bank number
talkMIDI(0xC0, 13, 0); // Set instrument number, 13 = marimba here.
noteOff(0, (lastX+5*lastY+MIDIoffset), 0);
noteOn(0, (currentX+5*yPosition+MIDIoffset), 127);
#endif
//MIDImessage(noteON,(lastX+5*lastY+MIDIoffset),0);//turn off last note
//MIDImessage(noteON,(currentX+5*yPosition+MIDIoffset),velocity);//turn on next note
}
lastX = currentX;
lastY = yPosition;
}
// end merge
void loop() {
if (firstPress){
checkFirstButton();
}
else{
for (byte pixel=0;pixel<8;pixel++){
if (pixel==yPosition){
ledData[pixel]=xPosition;
}
else{
ledData[pixel] = 0;
}
}
checkAccelerometer();
moveXPixel(getTime(xAcc));
moveYPixel(getTime(yAcc));
checkMIDI();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment