Wednesday, February 4, 2015

Using the Arduino UNO as a Guitar Pedal










This is a project a built a few years ago and I almost lost the code to it so i'm going to archive it here. I built a really lowfi bitcrushing guitar pedal using the Arduino UNO. There were a lot of challenges with this because the ADC's are and processor are slow and there are not true digital to analog outputs. This cannot be done with the Arduino UNO alone and needs some supporting circuits around it. Mainly a SPI Digital to Analog Converter and a DC offset circuit on the input. (Remember from a previous post that the ADC's cannot read negative voltage so you have to boost it up on a DC signal.)

You can get the full code Here: Arduino UNO Guitar Pedal

I'm not going to cover in detail most of the peripheral circuits since most people will customize this if you do build it. The main thing i'll cover though is the MCP4911 DAC. Here is how to wire the DAC. I recommend getting one of the Arduino developer shields to do this project.


You need to also build a DC offset for the input. I used an opamp designed for single supply projects (LM358).This looks like the following and it's output goes into A0 on the UNO.
R1 and R4 = 1k
R2 = Trim Pot - I used a trim pot to get the right dc offset.
R3 = I think i just put this to GND on mine.
VCC = The 5V source from the Arduino.

Now for the Arduino code.

There is a lot of code to get this project working so i'm going to step through it bit by bit. Click Here for the Full Code. I'll start with the ADC code in the setup function. For speed i'm only doing 8 bit conversions. Most of this code is borrowed from other blogs. This was so long ago i can't remember where i got it (Maybe HERE)but i don't take credit for creating all of this. I may have tweeked it a bit. Below is my setup function. This is using the Timers library to trigger samples.


#include <spi .h=""> #include <timerone .h=""> #define cbi(sfr, bit) (_SFR_BYTE(sfr) &amp;= ~_BV(bit)) #define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit)) volatile unsigned int input; //Analog input volatile unsigned int pot1; //stored value of the Left Potentiometer volatile unsigned int pot2; //stored value of the Right Potentiometer volatile boolean newSample=false; // new sample check volatile byte ibb; //used to force a slight delay on the ADAC conversions. volatile byte LSW; //store the value of the left momentary switch volatile byte RSW; //store the value of the right momentary switch int LED = 2; // Led to indicate if the effect is on or off int LeftSW=4; //left momentary foot switch input int RightSW=3; //right momentary foot switch input boolean RightDown=false; boolean LeftFown=false; boolean RightToggle = false; boolean LeftToggle = false; void setup() { Serial.begin(115200); pinMode(10, OUTPUT); pinMode(8,OUTPUT); pinMode(LED,OUTPUT); pinMode(RightSW, INPUT); pinMode(LeftSW, INPUT); digitalWrite(8, LOW); // set adc prescaler to 64 for 19kHz sampling frequency cbi(ADCSRA, ADPS2); sbi(ADCSRA, ADPS1); sbi(ADCSRA, ADPS0); cbi(ADMUX,ADLAR); // 8-Bit ADC in ADCH Register cbi(ADMUX,REFS0); // VCC Reference sbi(ADMUX,REFS1); cbi(ADMUX,MUX0); // Set Input Multiplexer to Channel 0 cbi(ADMUX,MUX1); cbi(ADMUX,MUX2); cbi(ADMUX,MUX3); // Timer2 PWM Mode set to fast PWM cbi (TCCR2A, COM2A0); sbi (TCCR2A, COM2A1); sbi (TCCR2A, WGM20); sbi (TCCR2A, WGM21); cbi (TCCR2B, WGM22); // Timer2 Clock Prescaler to : 1 sbi (TCCR2B, CS20); cbi (TCCR2B, CS21); cbi (TCCR2B, CS22); Timer1.initialize(33); // ~= 15k*2 Hz Timer1.attachInterrupt( Sample ); SPI.begin(); SPI.setBitOrder(MSBFIRST); SPI.setDataMode(SPI_MODE0); } </timerone></spi>
This code sets up the SPI interface for the DAC as well as all the prescalers for the ADC. Its a little complicated if your new to the Arduino but you can copy and past this and it should work.

Next we have the sample function. Notice this is referenced in setup by the Timer to trigger this function once the intercept happens. Once the timer interval runs it will start sampling the ADACs. The sample function also samples the Audio on every other intercept. Audio is sampled on the first intercept; The next intercept will sample the knob values. The two knobs are not sampled at the same time but every other time as well. The sequence is defined below in the comments. You can get away with this since the knobs don't need to have the same sample rate as the audio. By doing it this way each knob is sampled at half the frequency of the audio.

///The Sample Interrupt Function /// THis function does all the sampling. /// I have Two knobs on this effect pedal that can control /// different parameters. All together i'm using Three analog /// inputs. This code is a little hacked but i'll describe what /// I'm doing here: /// 1) I read the audio input every other time the sample is triggered /// 2) I read the knob input every time the sample is not being read. /// The knobs are alternated as well when it comes to reading. /// Example Sequence: /// intercept1 : Audio input is read /// intercept2 : Knob 1 is read /// intercept3 : Audio input is read /// intercept4 : Knob 2 in read. /// .... /// /// volatile boolean samplesw = true; volatile boolean potsw=false; boolean isFirst=true; void Sample(){ if(samplesw){ samplesw=false; input = ADCL; input = input | (ADCH &lt;&lt; 8); if(potsw) sbi(ADMUX,MUX0); // read Pot1; B000 B001 else sbi(ADMUX,MUX1); newSample=true; }else { samplesw=true; if (potsw){ potsw=false; pot2 = ADCL; pot2 = pot2 | (ADCH &lt;<8 0="" analog="" b000="" b010="" back="" before="" cbi="" conversion="" delay="" digital="" else="" ibb--="" ibb="" inputs="" leftsw="" lsw="(PIND" next="" pot1="pot1" potsw="true;" read="" rightsw="" rsw="(PIND" sbi="" set="" short="" start="" to="" xmp=""> I have two monetary switches and two knobs on my pedal. That means I need 3 analog inputs and two digital ones. This is the reason for the following global variables. You may assign your inputs on different pins. <xmp> volatile unsigned int input; //Analog input volatile unsigned int pot1; //stored value of the Left Potentiometer volatile unsigned int pot2; //stored value of the Right Potentiometer volatile boolean newSample=false; // new sample check volatile byte ibb; //used to force a slight delay on the ADAC conversions. volatile byte LSW; //store the value of the left momentary switch volatile byte RSW; //store the value of the right momentary switch int LED = 2; // Led to indicate if the effect is on or off int LeftSW=4; //left momentary foot switch input int RightSW=3; //right momentary foot switch input boolean RightDown=false; boolean LeftFown=false; boolean RightToggle = false; boolean LeftToggle = false;

Right Switch Code

I use the right momentary footswitch to turn the effect on an off. Almost a True bypass but i use an analog mutliplexer(MUX) which allows me to route the guitar input either into the Arduino or straight to the output. Most people would want to use a DPDT to achieve a true bypass but at the time I didn't have one laying around. I did however have a ton of analog multiplexers I bought off ebay to build an analog sequencer while back. If you choose to use the DPDT switch then you can leave off the Right Switch global above and the effectOn Code below. The Switch code will send the proper binary signal to the MUX to switch it to position one or two.

Below is the Right momentary switch Code. Notice I add delays to the code. This is to prevent bounce when the switch is initially pressed. Bounce happens when the contacts on the switch are just starting to touch. This can cause the switch to read on and off several times before finally resting. The delays prevents this and basically says "i don't care what the switch reads until the timeout is reached". ///Effect Switch Check /// Checks the button to see if the effect has been turned on. /// There is some delay times added here with the 'now' and 'rwait' variables. /// This to prevent the arduino from reading multiple presses while the switch /// is initially being pressed. When you first press the switch it can connect /// and disconnect multiple times right as the internal connections are being /// made. This will cause the pedal to be un predictable at selecting the next /// effect. /// Parameters: /// * now - The current time in milliseconds. This is to ensure delay to prevent bounce. int last=0; int rwait=0; boolean rdown=0; void effectOn(int now){ last = now; if(RSW == LOW &amp;&amp; (now - rwait) &gt; 15000){ //Serial.println("Down"); rwait = now; if(!rdown){ //Serial.println("Toggle"); RightToggle=!RightToggle; rdown=true; } }else if(RSW &gt; 0 &amp;&amp; (now - rwait) &gt; 15000 &amp;&amp; rdown){ //Serial.println("UP"); rdown=false; } if(RightToggle)// 76543210 PORTD = PORTD | B00100100; else PORTD = PORTD &amp; !(B00100100); }

Left Switch Code

And Here is the Left monetary FootSwitch Code. I use this to switch between different effects i've programmed in. //Effect Select... iterate though effects. /// There is some delay times added here with the 'now' and 'lwait' variables. /// This to prevent the arduino from reading multiple presses while the switch /// is initially being pressed. When you first press the switch it can connect /// and disconnect multiple times right as the internal connections are being /// made. This will cause the pedal to be unpredictable at selecting the next /// effect. /// Parameters: /// * NumEfct - The number of effects that are programmed minus 1. If you have 3 effects then this number should be 2. /// * now - This is the current time in milliseconds. This is to ensure delay to prevent bounce. int selected=0; int llast; int lwait; int ldown; int effectSelect(int NumEfct, int now){ llast = now; if(LSW == LOW &amp;&amp; (now - lwait) &gt; 15000){ //Serial.println("Down"); lwait = now; if(!ldown){ //Serial.println("Toggle"); LeftToggle=!LeftToggle; ldown=true; selected++; } }else if(LSW &gt; 0 &amp;&amp; (now - lwait) &gt; 15000 &amp;&amp; ldown){ //Serial.println("UP"); ldown=false; } if(selected &gt; NumEfct) selected =0; return selected; }

DAC Code

Next is the DAC code. This code works on the MCP4911 to send audio via SPI to the DAC. I use the built in Arduino SPI library for this. ///Write to DAC /// i use an external DAC and use the SPI out to write to it. /// The chip i use is the MPC4911 void writecmd(unsigned int value){ //digitalWrite(10,LOW); // 76543210 PORTB = PORTB &amp; !(B00000100); // set low int data = (value) &lt;&lt; 2; // The 4911 has certain bits that need to be set at the most significant bits. These are: // 0|1 write vs ignore // 0|1 unbuffered vs buffered // 0|1 2X gain vs no gain // 0|1 shutdown vs active mode // 0x7000 will give me write + buffered + 2x gain + active mode data = 0x7000 | data; //Serial.println(data, BIN); SPI.transfer(data&gt;&gt;8); //transfer the first 8 bits SPI.transfer(data); // transfer the last 8 bits //digitalWrite(10,HIGH); PORTB = PORTB | (B00000100); // set High }

Main Loop

Now we have the main Loop. All we need to do here is wait for a new sample and then process it. I always use the Left pot (pot1) to change effect parameters and the right pot (pot2) to change the gain. //Main Loop void loop() { while(!newSample); int now = micros(); newSample=false; effectOn(now); int sw = effectSelect(2, now); unsigned long output = 0; input = input * ((10 * pot2)/1023); // Change input gain if(sw==0){ output = sampleAndHold(input , pot1); //output=input; } else if(sw == 1){ output=andDistortion(input, pot1); } writecmd(output); }

Finally The Effects!!!

Later at the end of my code I Add all the effects: ///Effects //// Effect 0: Sample and Hold /// Used to lower the sampling rate int sah_i=0; int sah_hold; int sampleAndHold(int input, int holdTime){ int ht = holdTime/10; if(sah_i==0){ sah_hold=input; sah_i++; }else if(sah_i &lt; ht){ sah_i++; }else if(sah_i &gt;= ht){ sah_i=0; } return sah_hold; } //Effect 1: Logic Function distortion // This just does a logical AND with the Hex value in the array /// Use once of the knobes to change the value that will be anded /// the input. int crush[] = {0x3FF, //0 0x0EE,//1 0x0DD,//2 0x0BB,//3 0x077,//4 0x0CC,//5 0x099,//6 0x033,//7 0x066,//8 0x088,//9 0x011,//10 0x022,//11 0x044};//12 int andDistortion(int input, int selector){ int c = selector*20/1023; //Serial.println(c); return input&amp;crush[c]; } ///Effect 2: Sample and Hold with LFO /// Used to change the sample rate at the rate of the LFO /// Nothing new to add here since we are using effect0 plus some supporting functions //+++++++++++++++++++++++++++++++++++++++++++++++ //supporting functions //triangle LFO int samp=0; int sr=15000; int lfo_skip=0; int lfo_hold=0; int LFO(int _skip){ if(lfo_skip == 0){ lfo_hold++; lfo_skip++; if(lfo_hold &gt;= 100){ lfo_hold=0; } }else{ lfo_skip+=_skip+1; if(lfo_skip &gt;= 1000){ lfo_skip=0; } } //Serial.println(lfo_skip); return lfo_hold; }

The Power

The Arduino UNO has an onboard voltage regulator that can take at least 9volts DC. I added a normal BOSS power connector to the pedal and wired it as follows so that i can just plug it into my power chain with the rest of my guitar pedals.

THe USB Connector

I drilled a 3/8in hole on the side to allow me to reprogram it at anytime. Its just big enough to get the connector to the board.

Resources

Arduino UNO Full Code
MCP4911 DAC
Single Supply OpAmps
Analog Multiplexers
LoFi Guitar Pedal-Instructables

No comments:

Post a Comment