Controlling SGTL5000 (TWR-AUDIO-SGTL) using Teensy++ 2.0

Status
Not open for further replies.

robsoles

Well-known member
The Teensy++ 2.0 I am using is still running at 5V, the SGTL5000 has max voltage (including/espeically IO) of 3V6 and I have taken some care to make successful connections between GND, SDA & SCL on the devices without being too naughty, I'll happily detail how I think I've done that more than the code or picture I (intend to) attach to this post does if asked.

I appear to have basic control over the SGTL5000, config changes I make to VAG and VDDD measure appropriately - I drive VDDD to 0.8V and read pin 30 on SGTL5000 as very close to that value, change VDDD to 1.6V and sure enough the voltage on pin 30 is very close to the new value again, same with other offset voltages for various pins on the device.

I can route LINE IN to HEADPHONE directly by outputting 0x40 to CHIP_ANA_CTRL which appears to concur with the datasheet but nothing I have tried so far has made the source traverse the DAC, ie., pathway LINE IN->DAP->DAC->LINE OUT hasn't seen the source fed to LINE IN appear at LINE OUT in any way shape or form for me in quite a few hours of playing with this.

After much experimentation and wonderment I found that bits 12 & 13 (VOL_BUSY_DAC_LEFT & VOL_BUSY_DAC_RIGHT) are always set in CHIP_ADCDAC_CTRL no matter what I do, from the first moment I can query the register thru every attempted config change right to the final moment of letting my shoulders fall and shaking my head again.

The datasheet (http://cache.freescale.com/files/analog/doc/data_sheet/SGTL5000.pdf?pspll=1) says that those two bits have reset condition of 0x0 (termed that way in datasheet on page 34) but every time I power cycle (minimum 5 seconds off) the 'project' and make my first read of that register I get 0x323C which also indicates RSVD bits 7:4 are not in their stated reset condition of 0x0 either :(

The attached code is perhaps ugly/rudimentary/already-spaghettifying but it appears to function well enough, it makes input like ">0xe,0x200 <0xe" do the writing and reading of registers in the SGTL5000 and output like "Write 0xE=0x200 (newline) Read 0xE=0x3200". It also accepts "I" as the command to do my current idea/version of the initialisation sequence for SGTL5000 with values applying as follows;

CHIP_LINREG_CTRL,0x8
CHIP_ANA_POWER,0x7260
CHIP_LINREG_CTRL,0x60
CHIP_REF_CTRL,0x1F1
CHIP_LINE_OUT_CTRL,0x322
CHIP_PLL_CTRL,0x8000 (Edited in: I know I am not actually employing the PLL, this is a harmless (imho) leftover from an experiment I tried)
CHIP_CLK_TOP_CTRL,0x8
CHIP_ANA_POWER,0x57FF
CHIP_DIG_POWER,0x70
CHIP_CLK_CTRL,0xA
CHIP_ANA_CTRL,0x4
CHIP_SSS_CTRL,0x30
CHIP_ADCDAC_CTRL,0x200
DAP_CONTROL,0x1

I don't think it is really Teensy++ 2.0 support question but Paul has played with SGTL5000 before and I wrote to Freescale support 2 days ago, no reply yet; I posted to tower geeks forum yesterday and nobody read my post last I checked a few mins ago - I think they might be being snobs over the fact that I am not using any of their processor boards for tower thing; I was handed the bits I have (aside from my Teensy++ 2.0) as 'free bits', just in case you are wondering :rolleyes:

I already feel pretty embarrassed over this so don't spare my feelings, please just tell me where I've gone wrong if you can :)
 

Attachments

  • SGTL5000_I2C_Control.ino
    14.6 KB · Views: 180
  • IMAG1293.jpg
    IMAG1293.jpg
    115.8 KB · Views: 362
Last edited:
Oh, nice, the Freescale people have replied referring me to http://cache.freescale.com/files/analog/doc/app_note/AN3663.pdf?fpsp=1 which is the document that gave me the idea to try what I have tried so far - the only thing I'm seeing is that instead of relying on my last knowledge of contents of a register and just writing the value I think it needs to change to I could implement (at least a simile of) their modify method and use that more carefully than I have just been writing.

I wish the device datasheet didn't have so much conflicting information like "After reset this bit (STARTUP_POWERUP) can be cleared if VDDD is coming from an external source." on page 42 and then "Must clear the LINREG_SIMPLE_POWERUP and STARTUP_POWERUP bits in 0x0030 register after power up, for this setting to produce the proper VDDD voltage" back on page 39. AN3663 isn't much better, I am not loving freescale at all atm.
 
Here is my not-yet-released code for the SGTL5000. This is a work-in-progress, but I can confirm it does put the SGTL5000 into working modes for I2S output to both headphones and line-level, and it works for I2S input from either the mic or line-level pins.

I'm not sure if this really answers your question, but hopefully some known-good code will help?

First, the .h defining the API:

Code:
#define AUDIO_INPUT_LINEIN  0
#define AUDIO_INPUT_MIC     1

class AudioControl
{
public:
        virtual bool enable(void) = 0;
        virtual bool disable(void) = 0;
        virtual bool volume(float volume) = 0;      // volume 0.0 to 100.0
        virtual bool inputLevel(float volume) = 0;  // volume 0.0 to 100.0
        virtual bool inputSelect(int n) = 0;
};

class AudioControlSGTL5000 : public AudioControl
{
public:
        bool enable(void);
        bool disable(void) { return false; }
        bool volume(float n) { return volumeInteger(n * 1.29 + 0.499); }
        bool inputLevel(float n) {return false;}
        bool muteHeadphone(void) { return write(0x0024, ana_ctrl | (1<<4)); }
        bool unmuteHeadphone(void) { return write(0x0024, ana_ctrl & ~(1<<4)); }
        bool muteLineout(void) { return write(0x0024, ana_ctrl | (1<<8)); }
        bool unmuteLineout(void) { return write(0x0024, ana_ctrl & ~(1<<8)); }
        bool inputSelect(int n) {
                if (n == AUDIO_INPUT_LINEIN) {
                        return write(0x0024, ana_ctrl | (1<<2));
                } else if (n == AUDIO_INPUT_MIC) {
                        //return write(0x002A, 0x0172) && write(0x0024, ana_ctrl & ~(1<<2));
                        return write(0x002A, 0x0173) && write(0x0024, ana_ctrl & ~(1<<2)); // +40dB
                } else {
                        return false;
                }
        }
        //bool inputLinein(void) { return write(0x0024, ana_ctrl | (1<<2)); }
        //bool inputMic(void) { return write(0x002A, 0x0172) && write(0x0024, ana_ctrl & ~(1<<2)); }
protected:
        bool muted;
        bool volumeInteger(unsigned int n); // range: 0x00 to 0x80
        uint16_t ana_ctrl;



        unsigned int read(unsigned int reg);
        bool write(unsigned int reg, unsigned int val);
};

Here's the actual code, which uses the Wire library to talk to the SGTL5000.

Code:
bool AudioControlSGTL5000::enable(void)
{
        unsigned int n;

        muted = true;
        Wire.begin();
        delay(5);
        Serial.print("chip ID = ");
        delay(5);
        n = read(CHIP_ID);
        Serial.println(n, HEX);

        write(CHIP_ANA_POWER, 0x4060);  // VDDD is externally driven with 1.8V
        write(CHIP_LINREG_CTRL, 0x006C);  // VDDA & VDDIO both over 3.1V
        write(CHIP_REF_CTRL, 0x01F1); // VAG=1.575 slow ramp, normal bias current
        write(CHIP_LINE_OUT_CTRL, 0x0322);
        write(CHIP_SHORT_CTRL, 0x4446);  // allow up to 125mA
        write(CHIP_ANA_CTRL, 0x0137);  // enable zero cross detectors
        write(CHIP_ANA_POWER, 0x40FF); // power up: lineout, hp, adc, dac
        write(CHIP_DIG_POWER, 0x0073); // power up all digital stuff
        delay(400);
        write(CHIP_LINE_OUT_VOL, 0x0505); // TODO: correct value for 3.3V
        write(CHIP_CLK_CTRL, 0x0004);  // 44.1 kHz, 256*Fs
        write(CHIP_I2S_CTRL, 0x0130); // SCLK=32*Fs, 16bit, I2S format
        // default signal routing is ok?
        write(CHIP_SSS_CTRL, 0x0010); // ADC->I2S, I2S->DAC
        write(CHIP_ADCDAC_CTRL, 0x0000); // disable dac mute
        write(CHIP_DAC_VOL, 0x3C3C); // digital gain, 0dB
        write(CHIP_ANA_HP_CTRL, 0x7F7F); // set volume (lowest level)
        write(CHIP_ANA_CTRL, 0x0136);  // enable zero cross detectors
        //mute = false;
        return true;
}

unsigned int AudioControlSGTL5000::read(unsigned int reg)
{
        unsigned int val;

        Wire.beginTransmission(SGTL5000_I2C_ADDR);
        Wire.write(reg >> 8);
        Wire.write(reg);
        if (Wire.endTransmission(false) != 0) return 0;
        if (Wire.requestFrom(SGTL5000_I2C_ADDR, 2) < 2) return 0;
        val = Wire.read() << 8;
        val |= Wire.read();
        return val;
}

bool AudioControlSGTL5000::write(unsigned int reg, unsigned int val)
{
        if (reg == CHIP_ANA_CTRL) ana_ctrl = val;
        Wire.beginTransmission(SGTL5000_I2C_ADDR);
        Wire.write(reg >> 8);
        Wire.write(reg);
        Wire.write(val >> 8);
        Wire.write(val);
        if (Wire.endTransmission() == 0) return true;
        return false;
}

bool AudioControlSGTL5000::volumeInteger(unsigned int n)
{
        if (n == 0) {
                muted = true;
                write(CHIP_ANA_HP_CTRL, 0x7F7F);
                return muteHeadphone();
        } else if (n > 0x80) {
                n = 0;
        } else {
                n = 0x80 - n;
        }
        if (muted) {
                muted = false;
                unmuteHeadphone();
        }
        n = n | (n << 8);
        return write(CHIP_ANA_HP_CTRL, n);  // set volume
}
 
Thanks very much Paul, I'm going to have to hunt down a regulator with 1.8>output>1.1 and get off the internal supply of VDDD, of this I become surer and surer as the moments pass - the TWR-AUDIO-SGTL comes set up with VDDIO and VDDA (via inductor) hooked into a single 3V3 supply the TWR-ELEV sets up and VDDD doesn't have a trace coming away from the pad on the PCB - errata (and other documents for that matter) for SGTL5000 say that external VDDD supply is called for in new designs and the behaviour of this SGTL5000 seems to indicate that configuring the internal regulator and trying to use it is just asking for trouble.

I made the thrilling discovery, since posting my last above, that every time I configure CHIP_LINREG_CTRL to 0x6A (manually set to VDDIO as source, obey override & output ~1.1V afaict) and then change CHIP_ANA_POWER from the initial 0x7260 to 0x57FF the device was clearing CHIP_LINREG_CTRL to 0x0 and resetting CHIP_ANA_POWER to 0x7060 - the only value involving having bits 4:0 set that I have seen this unit 'keep' for CHIP_ANA_POWER is 0x6AFF and some of the bits which are on in that value seem daft to me to keep on; using this value nothing I've tried so far clears those VOL_BUSY_DAC_(channel) bits (13:12) in CHIP_ADCDAC_CTRL anyway.

The other lesson I am taking away from that so far is that if I don't get the result I was really expecting I really need to read back all registers to verify them before bothering with much else.

I hope I can find a nice regulator here, I will laugh (or do I actually mean cry?) if I end up mounting an LM317-T on the proto PCB here to use its minimum output via very slender pick up wire onto the pad for VDDD.
 
Hey Paul, when you find the time can you please tell me the answers to these three questions:

When you power up SGTL5000, before writing any registers, what are the values of CHIP_ID & CHIP_ADCDAC_CTRL registers?
After your init sequence, at least by the time you are routing ADC->I2S_OUT & I2S_IN->DAC successfully, what is value of CHIP_ADCDAC_CTRL?
What are you using (device & frequency please) for SYS_MCLK ?


FYI:
I set up an LD1117DTTR adjusted to 1.80V and very carefully attached it to pin #30 of SGTL5000 and now init sequence;
Code:
Write CHIP_ANA_POWER,0x5040
Modify CHIP_REF_CTRL,0x1BA,0x1FF
Write CHIP_LINE_OUT_CTRL,0x321
Modify CHIP_REF_CTRL,0x1,0x1
Write CHIP_ANA_POWER,0x40FF
Write CHIP_DIG_POWER,0x70
Modify CHIP_CLK_CTRL,0x2<<2|0x2,0x3F
Write CHIP_ANA_CTRL,0x4
Write CHIP_SSS_CTRL,0x30
Write CHIP_ADCDAC_CTRL,0x200
Write CHIP_LINE_OUT_VOL,0xC0C
Write DAP_CONTROL,0x1
Results in being able to read back all values as I expect from writing them except those busy bits...
Code:
CHIP_ID=0xA011
CHIP_DIG_POWER=0x70
CHIP_CLK_CTRL=0xA
CHIP_I2S_CTRL=0x10
CHIP_SSS_CTRL=0x30
CHIP_ADCDAC_CTRL=0x[B]3[/B]200
CHIP_DAC_VOL=0x3C3C
CHIP_PAD_STRENGTH=0x555F
CHIP_ANA_ADC_CTRL=0x0
CHIP_ANA_HP_CTRL=0x1818
CHIP_ANA_CTRL=0x4
CHIP_LINREG_CTRL=0x0
CHIP_REF_CTRL=0x1BB
CHIP_MIC_CTRL=0x0
CHIP_LINE_OUT_CTRL=0x321
CHIP_LINE_OUT_VOL=0xC0C
CHIP_ANA_POWER=0x40FF
CHIP_PLL_CTRL=0x5000
CHIP_CLK_TOP_CTRL=0x0
CHIP_ANA_STATUS=0x117
CHIP_ANA_TEST1=0x1C0
CHIP_ANA_TEST2=0x0
CHIP_SHORT_CTRL=0x0
DAP_CONTROL=0x1
Oh, before you ask; Yes, I tried your init values (in spite of not having anything set up I could try I2S with yet AND my clock being 24.576MHz) in the sequence and timing shown in your AudioControlSGTL5000::enable(void) routine and I find those 'busy' bits still on - all of your values 'stick' as far as I scrutinised it afterwards too.
 
oh yay! A configuration that clears those VOL_BUSY_DAC_(channel) bits! Now I want to figure out why this works as opposed to everything the application notes and datasheet made me think should.

For information sake: A text file buried inside https://community.freescale.com/ser...-252160/TWRK60D100M_TWRAudioSGTL_Loopback.zip, found on page https://community.freescale.com/thread/81904 in a post by Carlos Nerl Castellanos, had an init sequence which clears bits 12 & 13 of CHIP_ADCDAC_CTRL register by the end of it; I was poking through the zip file looking for better clues and I noticed a filename: Line_In_DAP_AVC_HP_LineOut_48k.txt
Code:
This configuration enables:
- Line in
- DAP at line in
- AVC at line in
- Headphone amplifier
- Line out
- 48Khz audio

SGTL5000_INIT_SIZE (23)

SGTL5000_CHIP_ANA_POWER,
SGTL5000_CHIP_LINREG_CTRL,
SGTL5000_CHIP_REF_CTRL,
SGTL5000_CHIP_LINE_OUT_CTRL,
SGTL5000_CHIP_SHORT_CTRL,
SGTL5000_CHIP_ANA_POWER ,
SGTL5000_CHIP_DIG_POWER,
SGTL5000_CHIP_LINE_OUT_VOL,
SGTL5000_CHIP_CLK_CTRL,
SGTL5000_CHIP_I2S_CTRL,
SGTL5000_CHIP_SSS_CTRL,
SGTL5000_DAP_CONTROL,
SGTL5000_DAP_AUDIO_EQ,
SGTL5000_DAP_AVC_THRESHOLD,
SGTL5000_DAP_AVC_ATTACK,
SGTL5000_DAP_AVC_DECAY,
SGTL5000_DAP_AVC_CTRL,
SGTL5000_CHIP_ANA_ADC_CTRL,
SGTL5000_CHIP_DAC_VOL,
SGTL5000_CHIP_LINE_OUT_VOL,
SGTL5000_CHIP_ANA_HP_CTRL,
SGTL5000_CHIP_ADCDAC_CTRL,
SGTL5000_CHIP_ANA_CTRL





0x7260, //SGTL5000_CHIP_ANA_POWER
0x0068, //SGTL5000_CHIP_LINREG_CTRL
0x01EE, //SGTL5000_CHIP_REF_CTRL
0x0F22, //SGTL5000_CHIP_LINE_OUT_CTRL
0x1106, //SGTL5000_CHIP_SHORT_CTRL
0x72FB, //SGTL5000_CHIP_ANA_POWER
0x0073, //SGTL5000_CHIP_DIG_POWER
0x0F0F, //SGTL5000_CHIP_LINE_OUT_VOL
0x000A, //SGTL5000_CHIP_CLK_CTRL
0x0080|I2S_WORD_LENGTH, //CHIP_I2S_CTRL
0x0013, //SGTL5000_CHIP_SSS_CTRL
0x0001, //SGTL5000_DAP_CONTROL
0x0003, //SGTL5000_DAP_AUDIO_EQ
0x0A40, //SGTL5000_DAP_AVC_THRESHOLD
0x0014, //SGTL5000_DAP_AVC_ATTACK
0x0028, //SGTL5000_DAP_AVC_DECAY
0x0001, //SGTL5000_DAP_AVC_CTRL
0x0000, //SGTL5000_CHIP_ANA_ADC_CTRL
0x3C3C, //SGTL5000_CHIP_DAC_VOL
0x0C0C, //SGTL5000_CHIP_LINE_OUT_VOL
0x1818, //SGTL5000_CHIP_ANA_HP_CTRL
0x0000, //SGTL5000_CHIP_ADCDAC_CTRL
0x0004  //SGTL5000_CHIP_ANA_CTRL
I changed CHIP_SSS_CTRL value to 0x30; the source I was (still) feeding to LINE IN turned up at LINE OUT and manipulating DAP settings had appropriate results so
Code:
    writeReg(CHIP_ANA_POWER,0x7260);
    writeReg(CHIP_LINREG_CTRL,0x0068); //SGTL5000_CHIP_LINREG_CTRL
    writeReg(CHIP_REF_CTRL,0x01EE); //SGTL5000_CHIP_REF_CTRL
    writeReg(CHIP_LINE_OUT_CTRL,0x0F22); //SGTL5000_CHIP_LINE_OUT_CTRL
    writeReg(CHIP_SHORT_CTRL,0x1106); //SGTL5000_CHIP_SHORT_CTRL
    writeReg(CHIP_ANA_POWER,0x72FB); //SGTL5000_CHIP_ANA_POWER
    writeReg(CHIP_DIG_POWER,0x0073); //SGTL5000_CHIP_DIG_POWER
    writeReg(CHIP_LINE_OUT_VOL,0x0F0F); //SGTL5000_CHIP_LINE_OUT_VOL
    writeReg(CHIP_CLK_CTRL,0x000A); //SGTL5000_CHIP_CLK_CTRL
    writeReg(CHIP_I2S_CTRL,0x0080); //|I2S_WORD_LENGTH); //CHIP_I2S_CTRL
    writeReg(CHIP_SSS_CTRL,0x0030); //SGTL5000_CHIP_SSS_CTRL //  0x13
    writeReg(DAP_CONTROL,0x0001); //SGTL5000_DAP_CONTROL
    writeReg(DAP_AUDIO_EQ,0x0003); //SGTL5000_DAP_AUDIO_EQ
    writeReg(DAP_AVC_THRESHOLD,0x0A40); //SGTL5000_DAP_AVC_THRESHOLD
    writeReg(DAP_AVC_ATTACK,0x0014); //SGTL5000_DAP_AVC_ATTACK
    writeReg(DAP_AVC_DECAY,0x0028); //SGTL5000_DAP_AVC_DECAY
    writeReg(DAP_AVC_CTRL,0x0001); //SGTL5000_DAP_AVC_CTRL
    writeReg(CHIP_ANA_ADC_CTRL,0x0000); //SGTL5000_CHIP_ANA_ADC_CTRL
    writeReg(CHIP_DAC_VOL,0x3C3C); //SGTL5000_CHIP_DAC_VOL
    writeReg(CHIP_LINE_OUT_VOL,0x0C0C); //SGTL5000_CHIP_LINE_OUT_VOL
    writeReg(CHIP_ANA_HP_CTRL,0x1818); //SGTL5000_CHIP_ANA_HP_CTRL
    writeReg(CHIP_ADCDAC_CTRL,0x0000); //SGTL5000_CHIP_ADCDAC_CTRL
    writeReg(CHIP_ANA_CTRL,0x0004);  //SGTL5000_CHIP_ANA_CTRL
Worked straight away - there is something stupid about this TWR-AUDIO-SGTL, I am sure, because I can run the init sequence I wanted
Code:
writeReg(CHIP_ANA_POWER,0x5040)
modReg(CHIP_REF_CTRL,0x1BA,0x1FF);
writeReg(CHIP_LINE_OUT_CTRL,0x321);
modReg(CHIP_REF_CTRL,0x1,0x1);
writeReg(CHIP_ANA_POWER,0x40EB);
writeReg(CHIP_DIG_POWER,0x70);
modReg(CHIP_CLK_CTRL,0x2<<2|0x2,0x3F);
writeReg(CHIP_ANA_CTRL,0x4);
writeReg(CHIP_SSS_CTRL,0x30);
writeReg(CHIP_ADCDAC_CTRL,0x200);
writeReg(CHIP_LINE_OUT_VOL,0xC0C);
writeReg(DAP_CONTROL,0x1);
straight after that one and the audio signal on LINE OUT just barely 'pops' and remains on, busy bits remain clear. Changes to DAP registers still have appropriate effect.

If I run the init sequence Paul is using
Code:
    writeReg(CHIP_ANA_POWER, 0x4060);  // VDDD is externally driven with 1.8V
    writeReg(CHIP_LINREG_CTRL, 0x006C);  // VDDA & VDDIO both over 3.1V
    writeReg(CHIP_REF_CTRL, 0x01F1); // VAG=1.575 slow ramp, normal bias current
    writeReg(CHIP_LINE_OUT_CTRL, 0x0322);
    writeReg(CHIP_SHORT_CTRL, 0x4446);  // allow up to 125mA
    writeReg(CHIP_ANA_CTRL, 0x0137);  // enable zero cross detectors
    writeReg(CHIP_ANA_POWER, 0x40FF); // power up: lineout, hp, adc, dac
    writeReg(CHIP_DIG_POWER, 0x0073); // power up all digital stuff
    delay(400);
    writeReg(CHIP_LINE_OUT_VOL, 0x0505); // TODO: correct value for 3.3V
    writeReg(CHIP_CLK_CTRL, 0x0004);  // 44.1 kHz, 256*Fs
    writeReg(CHIP_I2S_CTRL, 0x0130); // SCLK=32*Fs, 16bit, I2S format
    // default signal routing is ok?
    writeReg(CHIP_SSS_CTRL, 0x0010); // ADC->I2S, I2S->DAC
    writeReg(CHIP_ADCDAC_CTRL, 0x0000); // disable dac mute
    writeReg(CHIP_DAC_VOL, 0x3C3C); // digital gain, 0dB
    writeReg(CHIP_ANA_HP_CTRL, 0x7F7F); // set volume (lowest level)
    writeReg(CHIP_ANA_CTRL, 0x0136);  // enable zero cross detectors
those stupid busy bits go high/true and LINE OUT is silenced again, pretty sure that will be due to CHIP_CLK_CTRL setting being inappropriate for my setup and I shouldn't frown at Freescale for that detail, just all the other details I've noted about this TWR-AUDIO-SGTL rubbish thing.

Anyway, forget my impertinent questions from previous post please :)
 
I was hoping to do some EQ, like notches and so forth, with the SGTL5000 but I seem to have found out that their PEQ isn't up to the task.

It will work out fine, no doubt, for those wishing to use the I2S to manipulate the samples but for those wanting more advanced EQ without manipulating the samples themselves this is not the chip.

Their representatives were rather quicker at responding after the first response, right up until I challenged them to send me values for A1, A2, B0, B1 & B2 which could produce a specific -dB dip in a narrow-ish window centered around practically any frequency which isn't one of the 5 GEQ bands.

The equation described on page 22 of http://cache.freescale.com/files/analog/doc/data_sheet/SGTL5000.pdf?pspll=1 and the fact that they are using s.bbbbbbbbbbbbbbbbbbb (being 1 sign bit then decimal point followed by 19 bits), representing -1 thru to (~)0.99997 seems to ignore the fact that many filtering tasks will produce some values for A1, A2, B0, B1 & B2 which exceed +/-1

Example: Notch @ 1000Hz using http://arachnoid.com/BiQuadDesigner/
Code:
Filter Description
    Type
       Notch
    Notes
       Gain has no effect, Q works
    Constants
       b0               =        0.990763
       b1               =       -1.96457
       b2               =        0.990763
       a1               =       -1.96457
       a2               =        0.981526
    User Settings
       Q                =        7.00000
       Filter Gain      =        1.00000
       Center Freq. Hz  =     1000.00
       Sample Rate      =    48000.0
    Graph settings
       Start Hz         =       10.0000
       End Hz           =    20000.0
       Graph Vert. Gain =        1.00000
       Display Mode     = Linear

Home page: http://arachnoid.com/BiQuadDesigner
Copyright (C) 2011, P. Lutus - http://arachnoid.com
I will post back to this thread if Freescale reps prove me wrong - I hope they show me how to treat values given by whatever IIR/biquad tool for SGTL5000 coefficients if they can prove me wrong that their PEQ is very very limited.

Please feel free to prove me wrong, I will be so grateful to get to use the SGTL5000 - I am drawing up my own proto with a PCM3070 from Texas Instruments on it, my work will 'tack' it onto a panel of some or other job we will run through the place soon enough I guess. So far the datasheet for PCM3070 is making the SGTL5000 seem pretty user friendly; At least it looks like the TI people have equations (for filtering at least) that are better thought out.
 
happily wrong, I forever repent at leisure my foolishness in haste ;)

Freescale rep said:
Regarding your question, the user manual is not clear, I have suggested they update it. We require that all parameters (B0,B1,B2 and A1,A2) should be divided by two before you write them to registers no matter whether absolute A1 is greater than 1 or not, the design team has considered the case that absolute A1 may be greater than 1.
I think my method was dividing each value by two effectively enough and my actual mistake was probably trying to apply s-domain figures where z-domain figures were really needed :eek:

I thought the tools (well, at least a couple of them) I found, when Freescale reps initially told me they didn't supply a tool and simply referred me to 3rd party tools for such a tool, were giving me z-domain figures but I was hasty as ever - I probably got some 'correct-enough' figures at some point when my code was botching the sign bit.

I haven't been able to translate between floating point, z-domain etc etc figures, to suitably quantized hexadecimal values for SGTL5000 PEQ even to this moment but my favorite Freescale representative attached a spreadsheet which is (it seems) their tool for 'PEQ design' and I have asked permission to post it because I think it will seriously value add this thread for sure.

Pleasingly enough my last version of code for loading PEQ values didn't need correcting once I had values given by Freescale's own tool, so far their tool and my attempt to apply the values given by it is going splendidly :D
 
Well, Freescale rep happily gave me permission to post the document and then I found it was 3.7Mb zipped and too big to post here that way and seems like nobody is interested anyway.

Between the documents that Freescale sent me and some other bits and pieces from the internet I've pieced together some working code for deriving filter coefficients on the fly and (of course) applying them, I've tested the 'peak eq', 'low shelf' and 'high shelf' bits fairly extensively but my current version has no defense against coefficients that break the imposed scale - if anybody expresses an interest while I am still paying attention I will happily post my work, if anybody really wants to see the xlsx spreadsheet Freescale rep sent me I can post it on a web server I have enough control of and post a link instead I guess.
 
Have you been following the new audio library thread? I know Paul wants to support more then just the freescale part but off loading the filtering to the SGTL5000 sounds like a good idea. Would be interested in seeing your code and the spreadsheet.
 
Thanks cartere, just the encouragement I was waiting for ;)

Here are the files the Freescale rep gave me permission to share freely: http://111.67.19.42/sgtl5000-peq.zip -< I might shift it to part of a web-service that has a domain associated with it but for now it can be grabbed there.

I tried all sorts of hair brained stuff in the complete batch of code I have at the moment and it is pretty badly spaghettified but to try to pull out the bits I think might be worth passing on:
Code:
#include <Wire.h>
#include <math.h>

typedef union {
  uint32_t uint1;
  int32_t int1;
} transInt;

typedef struct {
  uint8_t type;
  float dBm;
  float BW;
  float fC;
  uint32_t coef[5];
} eqStruct;

typedef union {
  uint16_t value;
  struct {
    uint8_t lo;
    uint8_t hi;
  };
} convert16to8s;

typedef union {
  uint32_t value;
  struct {
    uint8_t d[4];
  };
} uconvert32to8s;



// Defines for SGTL5000 Codec registers.
 #define CHIP_ID		0x0000
 #define CHIP_DIG_POWER		0x0002
 #define CHIP_CLK_CTRL		0x0004
 #define CHIP_I2S_CTRL		0x0006
 #define CHIP_SSS_CTRL          0x000A
 #define CHIP_ADCDAC_CTRL	0x000E
 #define CHIP_DAC_VOL		0x0010
 #define CHIP_PAD_STRENGTH	0x0014
 #define CHIP_ANA_ADC_CTRL	0x0020
 #define CHIP_ANA_HP_CTRL	0x0022
 #define CHIP_ANA_CTRL		0x0024
 #define CHIP_LINREG_CTRL	0x0026
 #define CHIP_REF_CTRL		0x0028
 #define CHIP_MIC_CTRL		0x002A
 #define CHIP_LINE_OUT_CTRL 0x002C
 #define CHIP_LINE_OUT_VOL	0x002E
 #define CHIP_ANA_POWER		0x0030
 #define CHIP_PLL_CTRL		0x0032
 #define CHIP_CLK_TOP_CTRL	0x0034
 #define CHIP_ANA_STATUS	0x0036
 #define CHIP_ANA_TEST1		0x0038
 #define CHIP_ANA_TEST2		0x003A
 #define CHIP_SHORT_CTRL	0x003C
 #define DAP_CONTROL		0x0100
 #define DAP_PEQ		0x0102
 #define DAP_BASS_ENHANCE	0x0104
 #define DAP_BASS_ENHANCE_CTRL  0x0106
 #define DAP_AUDIO_EQ		0x0108
 #define DAP_SGTL_SURROUND	0x010A
 #define DAP_FILTER_COEF_ACCESS 0x010C
 #define DAP_COEF_WR_B0_MSB     0x010E
 #define DAP_COEF_WR_B0_LSB     0x0110
 // 115Hz
 #define DAP_AUDIO_EQ_BASS_BAND0 0x0116
 // 330Hz
 #define DAP_AUDIO_EQ_BAND1     0x0118
 // 990Hz
 #define DAP_AUDIO_EQ_BAND2     0x011A
 // 3000Hz
 #define DAP_AUDIO_EQ_BAND3     0x011C
 // 9900Hz
 #define DAP_AUDIO_EQ_TREBLE_BAND4 0x011E
 #define DAP_MAIN_CHAN		0x0120
 #define DAP_MIX_CHAN		0x0122
 #define DAP_AVC_CTRL		0x0124
 #define DAP_AVC_THRESHOLD	0x0126
 #define DAP_AVC_ATTACK		0x0128
 #define DAP_AVC_DECAY		0x012A
 #define DAP_COEF_WR_B1_MSB     0x012C
 #define DAP_COEF_WR_B1_LSB     0x012E
 #define DAP_COEF_WR_B2_MSB     0x0130
 #define DAP_COEF_WR_B2_LSB     0x0132
 #define DAP_COEF_WR_A1_MSB     0x0134
 #define DAP_COEF_WR_A1_LSB     0x0136
 #define DAP_COEF_WR_A2_MSB     0x0138
 #define DAP_COEF_WR_A2_LSB     0x013A

//For Filter Type: 0 = LPF, 1 = HPF, 2 = BPF, 3 = NOTCH, 4 = PeakingEQ, 5 = LowShelf, 6 = HighShelf

  #define PEQ_LPF 0
  #define PEQ_HPF 1
  #define PEQ_BPF 2
  #define PEQ_NOT 3
  #define PEQ_PEQ 4
  #define PEQ_LOS 5
  #define PEQ_HIS 6
  


const uint16_t coefs_msb[]={DAP_COEF_WR_B0_MSB,DAP_COEF_WR_B1_MSB,DAP_COEF_WR_B2_MSB,DAP_COEF_WR_A1_MSB,DAP_COEF_WR_A2_MSB};
const uint16_t coefs_lsb[]={DAP_COEF_WR_B0_LSB,DAP_COEF_WR_B1_LSB,DAP_COEF_WR_B2_LSB,DAP_COEF_WR_A1_LSB,DAP_COEF_WR_A2_LSB};





float absf(float num) {
	return (num<0 ? -num:num);
}

void PEQcalc(uint8_t filtertype, float dB_Gain,float BW,float fC,uint32_t coef[]) {

// I used a resource like http://www.musicdsp.org/files/Audio-EQ-Cookbook.txt
// to make this routine, I tested most of the filter types and they worked. Such filters have limits and
// before calling this routine with varying values the end user should check that those values are limited
// to valid results.

  transInt tmp;
  
  Serial.print("\ndB Gain=");
  Serial.println(dB_Gain,DEC);
  Serial.print("BW=");
  Serial.println(BW,DEC);
  Serial.print("fC=");
  Serial.println(fC,DEC);
  
  
  float A;

  if(filtertype<PEQ_PEQ) A=pow(10,dB_Gain/20); else A=pow(10,dB_Gain/40);
  
  float W0 = 2*3.1415926*fC/48000; 
  float cosw=cos(W0);
  float sinw=sin(W0);
  float alpha = sinw*sinh((log(2)/2)*BW*W0/sinw);
  float beta = sqrt(2*A);

  float b0,b1,b2,a0,a1,a2;
  
  Serial.print("\nA=");
  Serial.println(A,DEC);
  Serial.print("W0=");
  Serial.println(W0,DEC);
  Serial.print("alpha=");
  Serial.println(alpha,DEC);
  
  switch(filtertype) {
  case PEQ_LPF:
    Serial.println("\nLow Pass");
    b0 = (1.0F - cosw) * 0.5F; // =(1-COS($H$2))/2
    b1 = 1.0F - cosw;
    b2 = (1.0F - cosw) * 0.5F;
    a0 = 1.0F + alpha;
    a1 = 2.0F * cosw;
    a2 = alpha - 1.0F;
  break;
  case PEQ_HPF:
    Serial.println("\nHigh Pass");
    b0 = (1.0F + cosw) * 0.5F;
    b1 = -(cosw + 1.0F);
    b2 = (1.0F + cosw) * 0.5F;
    a0 = 1.0F + alpha;
    a1 = 2.0F * cosw;
    a2 = alpha - 1.0F;
  break;
  case PEQ_BPF:
    Serial.println("\nBand Pass");
    b0 = alpha;
    b1 = 0.0F;
    b2 = -alpha;
    a0 = 1.0F + alpha;
    a1 = 2.0F * cosw;
    a2 = alpha - 1.0F;
   break;
  case PEQ_NOT:
    Serial.println("\nNotch");
    b0=1;
    b1=-2*cosw;
    b2=1;
    a0=1+alpha;
    a1=2*cosw;
    a2=-(1-alpha);
  break;
  case PEQ_PEQ:
    Serial.println("\nPeak EQ");
    b0 = 1 + (alpha*A);
    b1 =-2 * cosw;
    b2 = 1 - (alpha*A);
    a0 = 1 + (alpha/A);
    a1 = 2 * cosw;
    a2 =-(1-(alpha/A));
  break;
  case PEQ_LOS:
    Serial.println("\nLow Shelf");
    b0 = A * ((A+1.0F) - ((A-1.0F)*cosw) + (beta*sinw));
    b1 = 2.0F * A * ((A-1.0F) - ((A+1.0F)*cosw));
    b2 = A * ((A+1.0F) - ((A-1.0F)*cosw) - (beta*sinw));
    a0 = (A+1.0F) + ((A-1.0F)*cosw) + (beta*sinw);
    a1 = 2.0F * ((A-1.0F) + ((A+1.0F)*cosw));
    a2 = -((A+1.0F) + ((A-1.0F)*cosw) - (beta*sinw));
  break;
  case PEQ_HIS:
    Serial.println("\nHigh Shelf");
    b0 = A * ((A+1.0F) + ((A-1.0F)*cosw) + (beta*sinw));
    b1 = -2.0F * A * ((A-1.0F) + ((A+1.0F)*cosw));
    b2 = A * ((A+1.0F) + ((A-1.0F)*cosw) - (beta*sinw));
    a0 = (A+1.0F) - ((A-1.0F)*cosw) + (beta*sinw);
    a1 = -2.0F * ((A-1.0F) - ((A+1.0F)*cosw));
    a2 = -((A+1.0F) - ((A-1.0F)*cosw) - (beta*sinw));
  }

  Serial.println("\nValues");
  Serial.print("b0=");
  Serial.print(b0,DEC);
  b0/=a0;
  Serial.print("/a0=");
  Serial.print(b0,DEC);
  b0/=2;
  Serial.print("/2=");
  Serial.print(b0,DEC);
  b0*=524288;
  tmp.int1=(int32_t)b0+0.5;
  coef[0]=tmp.uint1;
  Serial.print("=0x");
  Serial.println(tmp.uint1&0xfffff,HEX);

  Serial.print("b1=");
  Serial.print(b1,DEC);
  b1/=a0;
  Serial.print("/a0=");
  Serial.print(b1,DEC);
  b1/=2;
  Serial.print("/2=");
  Serial.print(b1,DEC);
  b1*=524288;
  tmp.int1=(int32_t)b1+0.5;
  coef[1]=tmp.uint1;
  Serial.print("=0x");
  Serial.println(tmp.uint1&0xfffff,HEX);
  
  Serial.print("b2=");
  Serial.print(b2,DEC);
  b2/=a0;
  Serial.print("/a0=");
  Serial.print(b2,DEC);
  b2/=2;
  Serial.print("/2=");
  Serial.print(b2,DEC);
  b2*=524288;
  tmp.int1=(int32_t)b2+0.5;
  coef[2]=tmp.uint1;
  Serial.print("=0x");
  Serial.println(tmp.uint1&0xfffff,HEX);

  Serial.print("a0=");
  Serial.print(a0,DEC);
  Serial.print("/a0=");
  Serial.print(a0/a0,DEC);
  Serial.print("/2=");
  Serial.print((a0/a0)/2,DEC);
  Serial.println("=discarded...");

  Serial.print("a1=");
  Serial.print(a1,DEC);
  a1/=a0;
  Serial.print("/a0=");
  Serial.print(a1,DEC);
  a1/=2;
  Serial.print("/2=");
  Serial.print(a1,DEC);
  a1*=524288;
  tmp.int1=(int32_t)a1+0.5;
  coef[3]=tmp.uint1;
  Serial.print("=0x");
  Serial.println(tmp.uint1&0xfffff,HEX);
  
  Serial.print("a2=");
  Serial.print(a2,DEC);
  a2/=a0;
  Serial.print("/a0=");
  Serial.print(a2,DEC);
  a2/=2;
  Serial.print("/2=");
  Serial.print(a2,DEC);
  a2*=524288;
  tmp.int1=(int32_t)a2+0.5;
  coef[4]=tmp.uint1;
  Serial.print("=0x");
  Serial.println(tmp.uint1&0xfffff,HEX);

}

uint16_t readReg(uint16_t regNum) {
  convert16to8s temp;
  temp.value=regNum;
  Wire.beginTransmission(SGTL5000);
  Wire.write(temp.hi);
  Wire.write(temp.lo);
  if(Wire.endTransmission()) return 0xFFFF;
  if(Wire.requestFrom(SGTL5000,2)!=2) return 0xFFFF; // chances of actual register reading all bits on are less likely than all off.
  temp.hi=Wire.read();
  temp.lo=Wire.read();
  return temp.value;
}

uint8_t writeReg(uint16_t regNum, uint16_t value) {
  convert16to8s temp;
  temp.value=regNum;
/*
  Serial.print("0x");
  Serial.print(regNum,HEX);
  Serial.print("=0x");
  Serial.println(value,HEX);
*/  
  
  Wire.beginTransmission(SGTL5000);
  Wire.write(temp.hi);
  Wire.write(temp.lo);
  temp.value=value;
  Wire.write(temp.hi);
  Wire.write(temp.lo);
  return Wire.endTransmission();
}

uint8_t modReg(uint16_t regNum, uint16_t value, uint16_t mask) {
  uint16_t inval=readReg(regNum)&(~mask); // I prefer a positive mask rather than negative (or perhaps opposite)
  return writeReg(regNum,inval|value);
}


void loadPEQx(uint8_t PEQnum, uint32_t vals[]) {
  
  uconvert32to8s tmp;
  convert16to8s tmp1;
  for(uint8_t t=0;t<5;t++) {
    tmp.value=vals[t];
    tmp.value=tmp.value<<4;
    tmp.d[0]=tmp.d[0]>>4;
    tmp1.hi=tmp.d[2];
    tmp1.lo=tmp.d[1];
    writeReg(coefs_msb[t],tmp1.value);
    tmp1.hi=0;
    tmp1.lo=tmp.d[0];
    writeReg(coefs_lsb[t],tmp1.value);
  }
  // writeReg(DAP_FILTER_COEF_ACCESS,(uint16_t)PEQnum);
  writeReg(DAP_FILTER_COEF_ACCESS,(uint16_t)0x100|PEQnum);
  delay(10);
  writeReg(DAP_FILTER_COEF_ACCESS,(uint16_t)PEQnum);
}


/* The above can be used like the following...
  eqStruct eqBass;

  eqBass.type=PEQ_LOS;
  eqBass.dBm=0;
  eqBass.BW=1;
  eqBass.fC=330;
  PEQcalc(eqBass.type,eqBass.dBm,eqBass.BW,eqBass.fC,(uint32_t*)eqBass.coef);

  loadPEQx(0,eqBass.coef);

*/

It should be noted that SGTL5000 registers DAP_CONTROL, DAP_PEQ & DAP_AUDIO_EQ need to be set accordingly to use the PEQ function in the SGTL5000.

The eqBass example given relies on a pot influencing 'eqBass.dBm' with subsequent calls to PEQcalc & loadPEQx when the value changes.
If anybody expresses an interest I will tidy up a batch of the code to post a complete working example.
 
Last edited:
@robsoles - please email me directly, paul at pjrc dot com, and I'll arrange for PJRC to send you a free Teensy 3.1 and the new audio board (which uses the SGTL5000). The DAP is one part of the SGTL5000 I'll probably never have time to touch. If you're willing to help, I'd really like to see this work with SGTL5000 DAP integrated into the audio library's AudioControlSGTL5000 object.
 
@robsoles
Can I just stick the HEX numbers in the excel sheet in the load_peq function or do I absolutely need to use filterCalc ?

Thanks !
 
Yes.

I verified the output of the original filter coefficient calculator, posted above, using the spreadsheet.

calcBiquad, currently in Audio Library, is a better version of the calculator than filterCalc but I would not claim it to be better or more accurate than the spreadsheet the guys at freescale sent to me - I would claim that calcBiquad makes acceptable output allowing (nearly) infinite filter shifting/changing on the fly.
 
thanks !
It's for a research project at University and the filter won't change and needs to be quite precise so I'll stick with the numbers from the Excel sheet.
 
I noticed that no configuration attempt is made for CHIP_ANA_ADC_CTRL. I reported on another thread an issue with quantization noise.

I now believe that it is perhaps a matter of setting the input and output level more adequately.

I might suggest that the AudioControlSGTL5000 class should implement a setInputLevel method that in turns configures this setting, since it is likely essential.

A person intending to use the audio shield for effects processing or equalization would be compelled to set input and output gains so that the input clips just at the very threshold of a given reference line level (say 1.41 volts for consumer line level) and then set the output according to the next stage's requirements.

I would be happy to become a contributor to this effort in a more active way as time permits.

-Aurelio
 
Your post would probably fare better in the http://forum.pjrc.com/threads/24793-Audio-Library/page20 thread.

True that, CHIP_ANA_ADC_CTRL is left in its initial state and I haven't made an addition to the library to allow the user to manipulate ADC level - added to my todo list tho I do not consider it essential.


Please define exactly what you mean by quantization noise and provide an example to demonstrate - what do you think is being misrepresented by poor quantization? (look at http://en.wikipedia.org/wiki/Quantization_(signal_processing) if you haven't already.)

I can make the signal clip and I can do a great deal (everything I can think of anybody trying to do with an audio codec) without causing clipping in (or coming out of) the SGTL5000.
 
Rob, just a brief follow up to your questions on this thread for continuity's sake followed by the results of my own investigation.

First: I tried various input levels for the signal output of an iPhone (A pretty typical line level source I suppose)

Turns out that the default (which appears identical to a call write(CHIP_ANA_ADC_CTRL, 0x0000); is already a good level, so that going up by as little as one lsb on each channel (passing 0x0011) induces clipping. Input levels are fine as it is for my scenario.

Second: When I set the output gain adequately for my application (audioShield.volume(80);) which is about 30ish decibels below the maximum output the chip is capable, the background noise, while not whisper quiet and power supply dependent is quite acceptable. Also, as implied in the last sentence, there is a component of the noise floor that is power source dependent, for instance, the floor has some "hash" when the power source is a laptop's USB jack. This is expected, and once more, well within what I call usable, especially for 35$.

Third and more important: The reason why my mind went to quantization noise: with the board plugged into a USB power source that isn't super clean and regulated (Say your average laptop) *AND* the volume set to 100 which granted will bring the noise floor up for scrutiny; when my board receives silence from an iphone there is *much more* noise than if my iphone's output is a low level 18k sinewave. All that is needed is the pass thru sketch, set the volume to 100, feed a pure silent signal from an iphone (which surely has a very low but not perfect quiescent noise floor) and compare that against a signal that is hard to hear but much higher peak to peak voltage, like an 18k sinewave. My expectation here is that the background noise coming out (at increased gain) should be low BOTH with an input "bias" signal like my 18k sinewave as well as without any such signal.

In other words, I can feed the board a signal that is critically in audible and make the noise floor go *down* since a sine at 18k can be harder to hear than the noise floor itself. If the noise floor can go down in that scenario, why isn't it always down? Quantization noise, in my mind, is one scenario that leads to this type of thing. But semantics aside, without hooking the codec to an AP to measure precisely, the noise floor I hear is not congruent with the specs on the datasheet for that codec.

Why would the noise floor go down in the presence of an input signal and go UP in absence of an input signal? Perhaps there is some other feature on the codec that causes the input impedance to jump up when the input levels are way low and what I hear is not quantization but merely self noise that is allowed in once the input crosses a low enough threshold??

Maybe if this is a conversation worth continuing we can create a separate thread somewhere more reasonable?

Thanks!

-Aurelio
 
If the input isn't driven (ie., the device it is plugged into isn't driving its signals to a DC steady state while trying to play 'silence') then it may be that every mm of conductive material connected to any of the inputs on the SGTL5000 are acting like antennas, even the lead between the source and inputs, picking up and amplifying noises like 60 (or 50 pending your locale) Hz hum from AC and (let's call them) emissions you can find monitors and computers (and mobiles and plenty other electronic devices) making.

When I connect my phone's output and put it on my desk at work, while it isn't playing anything, the different tones and some intervals in tones can be at least a little fascinating. When I do play something it manages near enough silence in places the artists do that.
 
Thanks Rob, I am well aware and I understand that an unconnected input, or an input connected to a high impedance output will pick up noise.

I am not describing what happens when the iphone falls asleep or when teensy audio board input is unplugged. I am describing a behavior with the output active (which should be at low impedance, near zero volts) and then slowly ramping up to a signal and then back down. I am using a signal generator app on the iphone. I appreciate the skepticism, but I already ruled out things I could not explain based on what I could investigate.

I should assume that you tried the experiment with various sources of power and line outputs and did not reproduce the problem?
 
audioShield.adc_hpf(true, false); cleans up the noise at the lowest end. While there are still normal sources of noise present, the grainy quality that creeps in as the volume goes down (which was the loudest component of the noise!) is completely gone with the HPF disabled.

The tradeoff is that users who do that will have to be careful to tolerate well DC offset in their code, (assuming there is any) in their application. For that matter, implementing a 32 bit precision HPF with the AudioFilter class is easy and for the type of application I am working on I much rather use that than the built in HPF, if it sounds like that.

Hopefully my findings will save other users a little bit of time.

-Aurelio
 
audioShield.adc_hpf(true, false); cleans up the noise at the lowest end.

Hello all,

Aurelio's solution sounds amazing, but I am having trouble implementing it:

There is this code in my Setup():
Code:
  //Activate Audio Shield
  audioShield.enable();
  audioShield.volume(100);
  delay(1000);  
  audioShield.inputSelect(AUDIO_INPUT_LINEIN);
[B]  audioShield.adc_hpf(true,false);[/B]

and I get the following error:
Code:
audioShield.adc_hpf(false);
^
exit status 1
'class AudioControlSGTL5000' has no member named 'adc_hpf'

I am working with a teensy 2.0 board, in case it makes a difference!

Thanks a million to anyone who could help!

Best,
Clément
 
The right code seems to be "audioShield.adcHighPassFilterDisable();"
Unfortunately It does bring any imprvement in my case :(
 
Hi robsoles, I know this is ancient history, but I don't suppose you could dig up this spreadsheet again? The link is currently dead. If you could find it, I'd be happy to post it somewhere where it could be reachable for eternity.

Thanks, Shawn
 
Status
Not open for further replies.
Back
Top