Jump to content

digital attenuator


kevin gilmore

Recommended Posts

On 3/20/2017 at 7:23 AM, Kerry said:

I've got my controller boards working pretty well now with the digital attenuators.  I like the larger one because it has a really nice feel as you turn the volume knob (...yes I see it :P).

I went and added a USB input to the board itself, though this is really overkill - which is why I did it.

IMG_0315.thumb.JPG.0d463cef57a0e76e4e32c49ab3835d8b.JPG

 

Here I'm updated with the TX / RX LEDs blinking away.

IMG_0313.thumb.JPG.f843a2dd0446734d726b1df0d22eb590.JPGIMG_0314.thumb.JPG.49108924af68518ce41335f0f66addb4.JPG

 

EDIT: I've got to do one more round of cleaning...

Hey Kerry - Just wondering, what motorised encoder/pot are you using? I tried googling the # on the sticker, but didn't turn up anything >< Putting my BOM for the boards together now :)

Thanks mate.

 

edit: I’m going with this one in case anyone else is interested, https://au.mouser.com/ProductDetail/688-RK16812MG099

Edited by Aive
Update
Link to comment
Share on other sites

  • 2 weeks later...
  • 1 year later...
  • 11 months later...
On 6/16/2019 at 4:54 PM, sbelyo said:

I wouldn't mind doing a run for all the boards needed for a complete system with volume control and source selection.  I know Kerry was working on or has code for the volume control

Any plans for a group buy?

Any bare or assembled boards available for sale?

Anyone has an updated BOM or are any of the parts unavailable by now?

Edited by starcat
Link to comment
Share on other sites

Preparing 4 layer board out of the 3 layer attenuatorsmtv42flipground7.zip as follows. Any other tips that I should take care for, like impedance controlled PCB? 

Going with thickness of 1.6mm and 1oz copper for the top and bottom layers and 0.5oz per each of both mid layers. 

 

screenshot_22.thumb.jpg.d27834473f301a6685240e4bdf0d8f32.jpg

Link to comment
Share on other sites

  • 6 months later...
On 8/16/2011 at 11:45 PM, Kerry said:

Here's the code I used for the rotary encoder and the Uno board....

 

On 8/24/2011 at 2:41 PM, stv1756 said:

Here is code to control two attenuators (i.e. balanced) with a potentiometer. Wire pot pins 1 and 3 to 5v and ground, and the wiper to A0 (analog input 0). Should work for a single board as well. For balanced configuration jumper id pins 1 and 2 of the second board (to change the address). Based on the code previously posted by Kerry....

 

On 3/19/2017 at 4:40 PM, Kerry said:

Here's the code.  Just rename the PDF to Zip and expand it into a folder under your .../Documents/Arduino directory....

 

Blast from the past! After 10 years, I finally managed to build two v1.0 of these boards. A couple of questions, if anybody is still familiar with the project....

I have used the original code from @Kerry the get the relays clacking away with an encoder and Arduino Uno, I just needed to replace the deprecated wire.send with wire.write. But I really want to use a potentiometer. The code from @stv1756 won't build (incompatible types in assignment of 'int' to 'int [12]'), and it seems like Kerry's 'all in one' code is more advanced - but I'm not nearly clever enough to work out how to configure it for the v1.0 boards.

I can work out how to disable the OLED (comment out "active OLED = .... lines), but I'm still throwing compile errors (flexible array member 'Font::_data' not at end of 'class Font') which suggests it is still trying to include a display, and I'm obviously missing several configuration options.

Anybody got any hints, or known-good code that works with v1.0 and a potentiometer?

Relay Attenuator.jpg

Edited by Beefy
  • Like 1
Link to comment
Share on other sites

It's been a while since I looked at this, but I'm pretty sure there are just parameters you can set.  There should be some comments in the code for SPI vs I2C and you should set the version down to 1.0 or something like that.

I don't have time now to really jump in deep to this, but good luck :) 

  • Thanks 1
Link to comment
Share on other sites

1 hour ago, Kerry said:

It's been a while since I looked at this, but I'm pretty sure there are just parameters you can set.  There should be some comments in the code for SPI vs I2C and you should set the version down to 1.0 or something like that.

I don't have time now to really jump in deep to this, but good luck :) 

Luck is usually all I've got going for me! But I think I have worked it out. I can get a successful build with the all-in-one code in your post from 2017/3/12 using Arduino IDE 1.8.19. My only concern is some very unpleasant turn on/off behaviour. My relays all seem to mute on power down, but then first volume movement on power up temporarily opens full signal volume before settling down. I suspect this is something on the volume board themselves, rather than the software issue.

 

In any case, I have it set up with:

10k linear pot connected to A1, 5V and GND

 

Within VolumeController:

Line 101 and 102 both commented out; doesn't change anything, but keeps the program smaller.

Line 127, dattnActive = dattnv1 to set it to the V1 board. Not even sure this is necessary, but it works.

Line 147 myPot.setMinMax(0, 255); needed to allow 255 steps for maximum volume

 

Within DATTN.cpp

Line 105, vSet = 255 - v; needed to allow 255 steps for maximum volume

 

Within font.h:

Line 26 "unsigned char *_data[];" this is the only line that really stops it building, and can just be commented out.

 

Within rotarypot.cpp, some parameters need to be tweaked to give faster volume control. Otherwise, small volume changes take an excruciatingly long time with very gentle pot movements. Zero idle relay chatter with these aggressive settings, surprisingly enough. Currently I have changed:

Line 17 maxValue = 255

Line 21 potChangeThreashold = 1

Line 22 potLockThreashold = 5

 

Now, I just need to test balanced configuration. Seems to be just a change on line 126, from false to true:

dattnV1 = new DATTN(1.0, true);

Edited by Beefy
Link to comment
Share on other sites

  • 2 months later...

Just wanted to offer a quick update on this...... I couldn't get the "all in one" code from @Kerry to work perfectly with my setup; it always opened up the relays to full volume on power-up, and I couldn't work out why.

User fastfwd from SBAF took pity on me and wanted a distraction project, and produced some code for me that did the trick. He took an interesting approach to 'dejitter' that works beautifully, and we also added averaging of multiple pots readings and easy custom mapping of volume knob to attenuation to customise things to my setup. Here's the final code:

Spoiler
//
// Volume Controller
//
// Arduino Uno Code for Kevin Gilmore's 8-bit Attenuator V1.0
// https://www.head-case.org/forums/topic/9407-digital-attenuator/
//
// Board group buy organised by Kerry
// https://www.head-case.org/forums/profile/655-kerry/
//
// Code produced by fastfwd
// https://www.superbestaudiofriends.org/index.php?members/fastfwd.8009/
//
// Code tested and customised by Beefy
// https://www.superbestaudiofriends.org/index.php?members/beefy.10794/
// https://www.head-case.org/forums/profile/684-beefy/
//
// WARNING!
// Don't fire this thing up for the first time with valuable equipment connected!
// Test the outputs with a multimeter or scope first to make sure that the pot
// direction is correct, that the expected attenuation is achieved, and that
// the relays don't pass full volume on power-down, power-up, or during switching.

#include <Wire.h>

// Configuration.

#define REVERSE_CW_CCW  0   // Leave at 0 if pot rotation direction is correct,
                            // or set to 1 to reverse clockwise/counterclockwise
                            // volume effect.

#define DEJITTER    2       // Ignore pot position changes smaller than this.
                            // Make the value larger if the volume spontaneously
                            // changes without touching the pot, or if the pot
                            // seems overly sensitive and twitchy.

//   Code is prepared for two attenuator boards in balanced operation. Per board:
//   8 Omron G6SK (set/reset dual coil) latching relays
//   2 TI ULN2803A relay drivers (one for 8x2 SET, one for 8x2 RESET)
//   2 TI PCF8574A I2C I/O expanders feeding the relay drivers

// Rotary-pot analog input.  Reading the pot wiper across 0-5 V produces a value 0-1023.
#define POT_PIN     A1
#define POS_MIN     0                   // Full counterclockwise (max attenuation)
#define POS_MAX     1023                // Full clockwise (min attenuation)
#define POS_RANGE   (POS_MAX - POS_MIN)
#define NUM_READS   10                  // Number of reads for averaging, each read takes about 0.1 ms
                                        // Cannot be higher than 32, otherwise you may overflow 'total'

// 8574A I2C device addresses:
//                             A2 A1 A0
#define LEFT_SET    0x38    //  0  0  0
#define RIGHT_SET   0x39    //  0  0  1
#define LEFT_RESET  0x3E    //  1  1  0
#define RIGHT_RESET 0x3F    //  1  1  1

// Relay values
#define RELAY_MIN       0       // Min attenuation (all relays off)
#define RELAY_MAX       255     // Max attenuation (all relays on)
#define LATCH_TIME_MS   4       // Minimum coil pulse width to latch the relays
#define OP_TIME_MS      10      // Minimum time between relay operations
#define RELAY_RANGE     (RELAY_MAX - RELAY_MIN)

int gCurrentPosition;   // Pot position that corresponds to the current state of
                        // the relays.  Whenever we read the pot position, we
                        // compare to this to see whether it's moved far enough
                        // to update the relays (i.e., whether it's moved at
                        // least DEJITTER counts).

void setup()
{
    Wire.begin();   // Initialize as an I2C master.

    gCurrentPosition = POS_MAX + DEJITTER;  // On startup, ensure that we'll
                                            // update the relays regardless of
                                            // the pot's position.
}

void loop()
{

    int total = 0;  // Cumulatively add data for averaging

    int pos;        // Pot position, 0-1023 (0 = full CCW, 1023 = full CW)
    int distance;   // Distance between pot position and gCurrentPosition

    int volume;     // Desired volume, 0-255 (0 = no attenuation,
                    // 255 = full -127.5 dB attenuation)

    // Read the position of the pot.  The readings aren't completely accurate,
    // so repeated readings may vary by one count (or maybe even a small number
    // of counts) even if the pot isn't deliberately moved.  To prevent the
    // relays from constantly hunting between adjacent values as they follow
    // these jittery readings, we filter out the jitter by only updating the
    // relays if the pot has moved more than DEJITTER counts away from the
    // position that corresponds to the current state of the relays.
    //
    // But this filtering method presents a problem:  If gCurrentPosition is
    // within DEJITTER counts of the min or max edge of the position range, the
    // pot won't have enough room to move DEJITTER counts toward the edge, and
    // therefore the relays will never be able to reach values at or near the
    // edges.  So when gCurrentPosition is within DEJITTER counts of the min or
    // max edge, we update the relays whenever the newly read position is
    // anywhere between gCurrentPosition and the edge.
    //
    // To ensure the jitter value can be kept low, multiple pot readings are averaged.

    for (int i=0; i < NUM_READS; i++){
      total += analogRead(POT_PIN);
    }

    pos = total / NUM_READS;
    distance = pos - gCurrentPosition;
    if (distance < 0) distance = -distance;
   
    if ((distance >= DEJITTER) ||
        ((POS_MAX - gCurrentPosition < DEJITTER) && (pos > gCurrentPosition)) ||
        ((gCurrentPosition - POS_MIN < DEJITTER) && (pos < gCurrentPosition)))
    {
        // Update gCurrentPosition.
        gCurrentPosition = pos;

        // Pot positions 0 to 1023 should logically map to relay positions 255 to 0.
        // This corresponds to the pot being 'off' when fully CCW, presenting no voltage to
        // the analog read pin, and maximum attenuation being obtained at high relay values.
        // If anything is wired incorrectly or the relay logic is backwards, the simplest
        // fix is to reverse the pot readings.

        if (REVERSE_CW_CCW) pos = (POS_MAX - pos) + POS_MIN;

        // Convert from POS to RELAY manually using mapped segements.
        // Comment out if using defined POS/RELAY values.

        volume = (pos >= 384) ? map(pos, 384, 1023,  63,   0) :   // Get Exstata from Buffalo to ~30 VMRS at 12:00 on the volume pot
                 (pos >= 127) ? map(pos, 128,  383, 127,  64) :   // Get from -63.5 dB to -32 dB in 1/4 of pot turn
                                map(pos,   0,  127, 255, 128) ;   // Get from -127.5 dB to -64.0 dB in 1/8 of pot turn

        // Convert from POS to RELAY using defined POS/RELAY values.
        // Comment out if using manually mapped segments.

        // volume = map(pos, POS_MIN, POS_MAX, RELAY_MAX, RELAY_MIN);

        // Update the relays.  We can't do them all at once, so we do them in
        // stages: First we close all the relays that should be closed, then we
        // open all the relays that should be open.  This way, the intermediate
        // state of the relays will never be louder (lower attenuation) than
        // the desired end state, which should help prevent loud pops while
        // updating.

        // Turn on the SET relay drivers.

        Wire.beginTransmission(LEFT_SET);   // Build the I2C transaction string
        Wire.write(volume);                 // for closing L+ and L- relays.
        Wire.endTransmission();             // Transmit it.

        Wire.beginTransmission(RIGHT_SET);  // Build the I2C transaction string
        Wire.write(volume);                 // for closing R+ and R- relays.
        Wire.endTransmission();             // Transmit it.

        delay(LATCH_TIME_MS);

        // Turn off the SET relay drivers, leaving the relays latched.

        Wire.beginTransmission(LEFT_SET);   // Build the string for turning off
        Wire.write(0x00);                   // L+ and L- SET drivers.
        Wire.endTransmission();             // Transmit it.

        Wire.beginTransmission(RIGHT_SET);  // Build the string for turning off
        Wire.write(0x00);                   // R+ and R- SET drivers.
        Wire.endTransmission();             // Transmit it.

        // Turn on the RESET relay drivers.

        Wire.beginTransmission(LEFT_RESET); // Build the I2C transaction string
        Wire.write(volume ^ 0xFF);          // for opening L+ and L- relays.
        Wire.endTransmission();             // Transmit it.

        Wire.beginTransmission(RIGHT_RESET);    // Build the I2C transaction string
        Wire.write(volume ^ 0xFF);              // for opening R+ and R- relays.
        Wire.endTransmission();                 // Transmit it.

        delay(LATCH_TIME_MS);

        // Turn off the SET relay drivers, leaving the relays latched.

        Wire.beginTransmission(LEFT_RESET); // Build the string for turning off
        Wire.write(0x00);                   // L+ and L- RESET drivers.
        Wire.endTransmission();             // Transmit it.

        Wire.beginTransmission(RIGHT_RESET);    // Build the string for turning off
        Wire.write(0x00);                       // off R+ and R- RESET drivers.
        Wire.endTransmission();                 // Transmit it.

        delay(LATCH_TIME_MS);

        // Datasheet gives a "Min set/reset pulse width" spec.  It's unclear
        // exactly what they mean, but a conservative interpretation would be
        // that it's the minimum time between consecutive operations on any
        // relay.  Wait here to ensure that the spec is satisfied.

        delay(OP_TIME_MS - LATCH_TIME_MS);
    }

    // If you want to alter the loop so the pot is read more or less frequently,
    // change "delay(x);" here, where 'x' is the number of milliseconds to wait
    // between pot readings.

    delay(10);

}

 

 

Everything is now all installed in my Exstata. I didn't originally plan to have these boards in there, so the layout is pretty wasteful, but it keeps clean signal lines away from AC. The digital power is four lines from two Twisted Pear LCDPS fed from two toroids - WAY over the top, but I had them all just sitting in the cupboard, so why not?

All in all a great end to a project first posted almost 11 years ago! :)

Volume Control Installed.jpg

 

[EDIT] I should add, I have no idea why this code works great, and Kerry's code was causing my boards to open up full volume on start-up. Such are the mysteries of the universe.

Edited by Beefy
  • Like 2
Link to comment
Share on other sites

  • 2 weeks later...
36 minutes ago, audiostar said:

Hey Beefy, nice you got it working, congratulations!

I was playing with the idea of using those boards myself. Just curious is the volume level remembered after power off and then subsequential power on? 

Well I'm using a pot for control, and it reads the pot value on startup. Wherever the pot is, it jumps to that volume pretty much immediately, which is the expected behavior.

Most implementations with an encoder do save and restore the volume. But I've really gone off encoders for this sort of application; don't like the physical feel, don't like not knowing the volume at a glance.

  • Like 1
Link to comment
Share on other sites

9 hours ago, Beefy said:

Well I'm using a pot for control, and it reads the pot value on startup. Wherever the pot is, it jumps to that volume pretty much immediately, which is the expected behavior.

That's nice. Which pot do you use and is it log or linear tapper, does that matter? Looks like a single channel pot will do.

I assume you completely power off the amp in between usage, so no standby trafos involved? 

Looking through the archives the attenuatorsmtv42flipground7 seems to be the latest version of the boards.

Edited by audiostar
Link to comment
Share on other sites

4 hours ago, audiostar said:

That's nice. Which pot do you use and is it log or linear tapper, does that matter? Looks like a single channel pot will do.

I assume you completely power off the amp in between usage, so no standby trafos involved? 

Looking through the archives the attenuatorsmtv42flipground7 seems to be the latest version of the boards.

I use a Bourns PDB181-E420K-103B - a bog standard, dirt cheap, linear taper 10K. Completely power off, no standby involved. You can use an audio taper pot easily, you would just have to be more creative with the volume/pos mapping.

Can't help you out with the latest version, I'm just a trained monkey building boards that Kerry organised! Just don't get the different versions mixed up. I built and tweaked code for the original v1 with the PCF8574A/ULN2803A combo. The v2 is a completely different chip (MAX4820) and software approach.

  • Like 2
Link to comment
Share on other sites

  • 4 months later...

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

Important Information

By using this site, you agree to our Terms of Use.