This page includes the description and code required to manage 4 PGA2310 volume controls, and a total of 8 channels!
First lets look at the hardware...

From Datasheet
/MUTE always remains high in my case, since to mute these chips you can also send the data value of 0x00h - minimum volume level. ZCEN also remains high since this keeps the volume control from making any pops on volume changes.
SDI, SCLK and /CS are connected to the microprocessor directly, and I label them as DATA, CLOCK and LATCH respectively from here onwards.
SDO goes to additional PGA2310's. Each addition one still needs a CLOCK and DATA signal, so these wires are paralleled with the ones connecting to the PGA2310 before.
If you want more information on how to connect additional PGA2310's, the datasheet explains this clearly. Don't forget the pull-up resistor!
I will dive straight into showing you the routine that outputs code to all four of my PGA2310 chips...
voidpga2310() {charn;// Loop counter// Set latch to lowclear_bit(portc, 2);shortbitSelect = 10000000b;// byte used to select a bit in voll/vol2 to send - MSB firstfor(n=0; n<64; n++) {// Check bit// Clear clock for next bitclear_bit(portc, 1);charsn; sn = n/8;// if n = 4, sn = 0// if n = 8, sn = 1// if n = 16, sn = 2// if n = 24, sn = 3// if n = 32, sn = 4// if n = 40, sn = 5// if n = 48, sn = 6// if n = 56, sn = 7// if n = 64, sn = 8charvol = 0;if(mute == 0) vol = volumes[sn];if((vol & bitSelect) != 0)// the set bit position in bitSelect in vol is set, output highset_bit(portc, 0);elseclear_bit(portc, 0);// Raise clock so serial bit output is sentset_bit(portc, 1);// Shift set bit in bitSelect one position to the leftbitSelect = bitSelect >> 1;if(bitSelect == 0) bitSelect = 10000000b; }// Set latch to highset_bit(portc, 2); }
8 bytes in total needs to be sent. One for each channel, representing a volume level from 0-255 (or -96dB to +31.5dB).
Each volume level is stored in an array, with position 0 being the subwoofer channel and 7 being front left (it works in reverse order from what you would suspect). Essentially, one 64-bit word is sent out, meaning this whole process can last a few hundred micro seconds (us) - but it is fast enough as in pratice I notice no delay from turning the encoder to having a change in volume.
Connections in the above routine are all on port c. Pin 0 is serial Data (SDI), pin 1 is Clock (SCLK) and pin 2 is Latch (/CS). They can be changed as you see fit.
This is quite a complex process, as this C compiler, and PIC's in general do not support floating point numbers natively - so having a decimal point becomes slightly difficult!
However, I worked a way around it, and the code is shown below for your convenience.
voidshowVolume(charvol) { lcdLine(3); lcdText('V'); lcdText('o'); lcdText('l'); lcdText(' ');intmodulus = 0;// Gain is 0dBif(vol == 192) { gain = 0; gainDec = 0; lcdText(' '); }// Gain is postiveelseif(vol > 192) { gain = 31 - ((255-vol) / 2); modulus = (254-vol) % 2;// Work out modulus - the remainder of the divisionlcdText('+'); }// Gain is negativeelseif(vol < 192) { gain = ((254-vol) / 2) - 31; modulus = (254-vol) % 2;// Work out modulus - the remainder of the divisionlcdText('-'); }// Work out the gain decimalif(modulus) gainDec = 5;elsegainDec = 0; lcdText('0' + (gain / 10) % 10 ); lcdText('0' + gain % 10 ); lcdText('.'); lcdText('0' + gainDec % 10); lcdText('d'); lcdText('B'); }
Its not tooo complex once you've scratched your head and studied it a bit, but in
case you don't care, there are a few things to point out if you want to use this code
in your project. lcdText(' ') is an important routine - this just takes
a simple character and writes it to an LCD - and it will of course vary depending on
the LCD you have. To make sure the text is wrote to the last line of my display, I
call a routine called lcdLine(3). This again will vary according to your
LCD.
Note this routine only takes one volume character - a sort of universal volume level unaffected by rear/sub/center volume cut or boost, or balance if you add that too. Since I have no balance, the character sent to this routine is one of the front levels (i.e. left).
Another array of 8 chars is used to represent cut/boost to certain speakers, such as rear, center or subwoofer.
voidsetLevels(charvol) {inty;for(y=0; y<8; y++) { volumes[y] = vol + levels[y];// Prevent overflowif(levels[y] & 0x80) {// Negative, new level should always be less than overallif(volumes[y] > vol) volumes[y] = 0; }else{// Positive, new level should be greater than overallif(volumes[y] < vol) volumes[y] = 255; } } }
This routine takes a universal volume level, and automatically applies the levels from the levels array. It deals with overflow, preventing any sudden change from min to max and vice versa.
The levels array is set from byte codes sent from PIC#1 using:
case'L':// Rear, Cen, Sub level modifiers// All rears go to first byteintm;for(m=2; m<6; m++) levels[m] = serialRxBuffer[1]; levels[0] = serialRxBuffer[2];// Cenlevels[1] = serialRxBuffer[3];// SubsetLevels(volumes[7]); pga2310();break;
Balance is ignored, hence the loop counter starts at 2, and applies the first byte to every rear channel. More details on how each level is set on PIC#1 is described later in the PIC control page.