2013. augusztus 1., csütörtök

Using Arduino with the 8th note Stepped Attenuator

The gateway to external displays

This text is about how to use an Arduino to read data from the 8th note Stepped Attenuator.
The Arduino is an easy to use programmable hardware, wich is very popular amongst DIY-ers.
If you want to make an external display for your attenuator then this is where you must start.



What is possible?
There are digital ports on the driver board. Using these ports it is possible to read the following:
-is MUTE on?
-is LULO active?
-is SRC2 selected?
-what is the attenuation level?

What is it good for?
You can use these data to output text on a display, or make a led bar show the attenuation level, or do whatever you can think of to do with. You can find inspiring ideas in the last blog entry.

What skills are needed?
You need some experience in using Arduino and programming it. If you are not familiar with Arduino, then stop at Arduino's webpage first. There are many good articles to beginners, helpful communities, user forums and lots of example codes on the internet, which can help you in the first steps.
Because there are many infos out there about Arduino, I will not duplicate it here. This article will focus on helping users to write the 8th note specific part of their own program.

What will we do?
We will read the state of the MUTE, LULO and SRC2 LEDs, which is the simplest task. We will read the digital out (DO) port and decode the attenuation level.

Lets start it!
First you have to solder two unpopulated headers into the board. The one called DO (1x3 pin) and located on the bottom right corner of the driver board,
the other has no name and located next to the status LEDs (2x3 pins). We will use these headers or at least part of it.



The "LED header" (lets call it that way) contains pins to read the LEDs status.
You need to wire these pins to GPIO input pins:

You have to check these pins state on the Arduino, it has the value either HIGH or LOW. Choose pins that are not needed later for something else.
Please note, the pins status are LOW when the function (and its LED) is active.
The state of the pins can be read with the following (well known) way:

//let the pins connected to the following:
int mutePin = 7;
int luloPin = 8;
int src2Pin = 9;

//store the pin state in these variables
int mute_State = 0;
int lulo_State = 0;
int src2_State = 0;

void setup()
{
  pinMode(mutePin, INPUT);      // sets the digital pin as input
  pinMode(luloPin, INPUT);      // sets the digital pin as input
  pinMode(src2Pin, INPUT);      // sets the digital pin as input
}

void loop()
{
  mute_State = digitalRead(mutePin);   // read the input pin
  lulo_State = digitalRead(luloPin);   // read the input pin
  src2_State = digitalRead(src2Pin);   // read the input pin
}

Now if you have the variables then you can change your display according to it.

This was the easy part.

How to get the attenuation level?
The attenuation level can be read on the DO header of the driver board.
The DO port uses standard SPI communication. If you are not familiar with SPI, then follow this link to the Arduino SPI reference:
http://arduino.cc/en/Reference/SPI

A short quote from it:
"Serial Peripheral Interface (SPI) is a synchronous serial data protocol used by microcontrollers for communicating with one or more peripheral devices quickly over short distances. It can also be used for communication between two microcontrollers."


This is what we will do.
The SPI protocol uses a Clock and a Serial data line. When the clock ticks, the serial line state in the receiver is stored in a register. When it ticks again, it stores the next value in the register, until 8 bits are received.



In our case, the Master (and transmitter) will be the 8th note Stepped Attenuator and the Slave (and receiver) will be the Arduino. The SPI protocol defines a Slave Select (SS) pin what we don't use, because we have only one slave.
The DO port has the SCK (serial clock), the MOSI (Master Out Slave In) and a GND (ground) pins.


Note that MOSI, and SCK are available in a consistent physical location on the Arduino's ICSP header. You need to wire these pins.

Arduino ICSP header pinout

When finished with the wiring, lets think a little together.
The following is important and specific to the 8th note Stepped Attenuator:
  • The attenuator transmits data only on state change, in other words, you can not request a transmission
  • The transmitted data consists of 16 bits or 2 bytes, and repeated once
  • The two byte long data can be decoded to return the attenuation level

The transmitted data bits are mixed for design reasons, and can be decoded in a quick function that returns a byte between 0 and 127. The attenuator thus has 128 levels, each level means -0.5dB attenuation. So the total attenuation is -63.5dB. You do not have to bother with the repeated transmission, it differs in its bits, but when decoded it returns the same value. You do not need to omit any transmission, just decode it again, it won't hurt.

To receive the data, the Arduino must be listening. It has to receive two bytes, and store it in two variables. When two bytes has been received, call the decode function, so it can refresh the attenuation level variable.
If this is done in an interrupt, then the attenuation level variable keeps refreshing automatically and your display code can use or process this variable.

This flow chart explains the process:
Flow chart of the Arduino SPI receiver and data decoder routine interrupt


You can test and edit the following code for your needs.
Please note this code has not been tested yet, because I don't have my Arduino at the moment. But if you test it, and send me a report, I will update the code with your name.

// Sample Arduino code for 8th note Attenuator
// Written by Kara László
// 22. July 2013.
// Tested by: UNTESTED
// at:

/*
PLEASE NOTE:
The attenuator sends data only on state change. There is no method for requesting transmission.
The attenuation level is stored in the aLevel variable.
(aLevel == 0) means no attenuation or full volume
(aLevel == 127) means full attnuation or lowest volume

To translate the aLevel to dB:
 0 :    0dB
 1 : -0.5dB
 2 :   -1dB
 3 : -1.5dB
 4 :   -2dB
...
126:  -63dB
127:-63.5dB
*/

#include <SPI.h>

volatile byte FirstByte = 0; //the first storage
volatile byte SecondByte = 0; //the second storage
volatile boolean FirstByteReceived = false; // True if first byte is stored, second is transmitting
volatile byte aLevel = 0; // the attenuation level (0-127)
byte aLevel_Temp = 0; //temp to be able to compare the next aLevel with
float adB = 0; //attenuation in dB only for display

void setup (void)
{
// have to receive in "Master Out Slave In" pin
pinMode(MOSI, INPUT);
// turn on SPI in slave mode
SPCR |= _BV(SPE);
// turn on interrupts
SPI.attachInterrupt();
// set up Serial library at 9600 bps
Serial.begin(9600);
Serial.println("Hello 8th note Stepped Attenuator!"); // prints hello with ending line break
} // end of setup

//decoder function
byte DecodeaLevelData(byte data1, byte data2)
{
unsigned int inData = (data2) | ((unsigned int)(data1)<<8);
unsigned char decodedData = 0;
decodedData |= (unsigned int)(0b0000000000000010 & (inData))>>1;
decodedData |= (unsigned int)(0b0000000000001000 & (inData))>>2;
decodedData |= (unsigned int)(0b0000000000010000 & (inData))>>2;
decodedData |= (unsigned int)(0b0100000000000000 & (inData))>>11;
decodedData |= (unsigned int)(0b0001000000000000 & (inData))>>8;
decodedData |= (unsigned int)(0b0000100000000000 & (inData))>>6;
decodedData |= (unsigned int)(0b0000001000000000 & (inData))>>3;
return decodedData; //sending back 0-128 level
}

// interrupt routine on SPI transmission end
ISR (SPI_STC_vect)
{
// grab byte from SPI Data Register & put it in temp
byte temp = SPDR;
//if first byte is received then store it in FirstByte
if (!FirstByteReceived)
        {
        FirstByte = temp;
        FirstByteReceived = true;
        }else{
//if second byte is received then store it in SecondByte
        SecondByte = temp;
//now we have two bytes that need to be decoded
//aLevel will have the attenuation level
        aLevel = DecodeaLevelData(FirstByte, SecondByte);
//reset byte counter
        FirstByteReceived = false;
        }
} // end of interrupt routine SPI_STC_vect

// start of main loop
void loop (void)
{
if (aLevel != aLevel_Temp)
    {
    //calculating attenuation in dB
    adB = ((float)aLevel * (-0.5));
    //printing message
    //will output like "Attenuation: 31 dB"
    Serial.print("Attenuation: ");
    Serial.print(adB, DEC);
    Serial.println(" dB");
    //stores aLevel to temp
    aLevel_Temp = aLevel;
    }
} // end of loop

Considerations:
If you use the Arduino's MOSI pin, it can not be used for another SPI communication.
But If you need that, you can implement the SPI in any GPIO pin by software. You will not have time issues because the attenuator SPI transmission is slow.
The microcontroller of the attenuator runs on only 1MHz, compared to the Arduino's 20MHz.

I hope this article was useful, and easy to understand. If you have any questions, contact me. If you have a project, or idea You would like to share with others, feel free to do so in the comments area.

Nincsenek megjegyzések:

Megjegyzés küldése