7.1 channel Hi-Fi microprocessor/PC controlled Preamp

Note: This project has been rebuilt and full information is available here.
I suggest reading about the rebuild first. Information below is for reference purposes.

LCD

An LCD is important for a microchip controlled preamp. Ok, it is possible to control the preamp without one, but knowing the current volume level, cutting/boosting channel levels, switching inputs, switching surround modes all becomes a bit more difficult. All good systems have an LCD anyway!

The LCD I choose wasn't that easy to program, especially as I only intended ever to use text on it. However this is a graphic display Vikay VK5121 LCD module with 32x120 dots. Writing ASCII text codes to it won't work!

I must admit, I needed help with this and after deep searching I found this link. It is exactly what I need to get the features of this LCD up and running - except I would need to convert from the CCS PIC C compiler syntax to the C2C PIC C compiler syntax - as they are not the same!

For completeness, I have the converted source code for the C2C compiler, see lcd.c and lcd.h.

Many thanks to the author for providing this - otherwise I might of had to get some new LCDs!

I added some extra functions, and changed a few things too.

Firstly, my version stretches the text... this is so it is easier to read (like a bold format), but halves the amount of text you are able to fit on the LCD line.

If you don't like this, change the following.

for (x=0; x<10; x++) to change to for (x=0; x<5; x++)
char_row = x/2; to change to char_row = x;

That will restore it back to the original configuration.

I also added lcdString() which allows a C string to be sent to the LCD, and lcdLine() which goes to an LCD line (0-3), deletes it ready for writing new text.

C2C Code for the volume

I will dive straight into showing you the routine that outputs code to all four of my PGA2310 chips...

void pga2310() {
    char n; // Loop counter
    
    // Set latch to low
    clear_bit(portc, 2);
    
    short bitSelect = 10000000b; // byte used to select a bit in voll/vol2 to send - MSB first
    
    for (n=0; n<64; n++) { // Check bit
        // Clear clock for next bit
        clear_bit(portc, 1);
        
        char sn;
        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 = 8
        
        char vol = 0;
        if (mute == 0)
            vol = volumes[sn];
        
        if ((vol & bitSelect) != 0) // the set bit position in bitSelect in vol is set, output high
            set_bit(portc, 0);
        else
            clear_bit(portc, 0);
        // Raise clock so serial bit output is sent
        set_bit(portc, 1);
        // Shift set bit in bitSelect one position to the left
        bitSelect = bitSelect >> 1;
        if (bitSelect == 0)
            bitSelect = 10000000b;
    }
    // Set latch to high
    set_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.

Calculating the volume in dB for the LCD

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.

void showVolume(char vol) {
    lcdLine(3);
    lcdText('V');
    lcdText('o');
    lcdText('l');
    lcdText(' ');
    int modulus = 0;
    
    // Gain is 0dB
    if (vol == 192) {
        gain = 0;
        gainDec = 0;
        lcdText(' ');
    }
    // Gain is postive
    else if (vol > 192) {
        gain = 31 - ((255-vol) / 2);
        modulus = (254-vol) % 2; // Work out modulus - the remainder of the division

        lcdText('+');
    }
    // Gain is negative
    else if (vol < 192) {
        gain = ((254-vol) / 2) - 31;
        modulus = (254-vol) % 2; // Work out modulus - the remainder of the division
        lcdText('-');
    }
    // Work out the gain decimal
    if (modulus)
        gainDec = 5;
    else

        gainDec = 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).

Levels - for cut/boost

Another array of 8 chars is used to represent cut/boost to certain speakers, such as rear, center or subwoofer.

void setLevels(char vol) {
    int y;
    for (y=0; y<8; y++) {
        volumes[y] = vol + levels[y];
        // Prevent overflow

        if (levels[y] & 0x80) { // Negative, new level should always be less than overall
            if (volumes[y] > vol)
                volumes[y] = 0;
        }
        else { // Positive, new level should be greater than overall

            if (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 byte
                int m;
                for (m=2; m<6; m++)
                    levels[m] = serialRxBuffer[1];
                levels[0] = serialRxBuffer[2]; // Cen

                levels[1] = serialRxBuffer[3]; // Sub
                setLevels(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.

Onto page 5 - Results