I came across a broken 3 dB attenuator and since I was curious of its internal design, I decided to do an autopsy, just like I did with the broken termination a few months ago. The attenuator is made by Mini-Circuits and it is a low-cost type useful up to 6 GHz. The manufacturer part number is VAT-3+. The data sheet says it costs $13.95.
The symptoms were that the female connector was an open circuit (bad) while the male connector had a resistance of 150 ohms to ground (which is what it should be for a 3 dB attenuator). This indicated an open circuit between the female connector and the internal resistive network.
It is perhaps not obvious how to take the attenuator apart, but I decided to start filing on the central tube (which seemed to be made out of brass) in the hope that I would then be able to pry it apart to reveal the interior.
This turned out to be successful and I could soon remove the tube. An alternative (maybe easier?) method might have been to remove the label and then drill out the four punch marks around the rims of the tube.
Inside the attenuator, there is a small substrate with four laser trimmed thick film resistors forming the pi-topology attenuator. See my article about pi and T attenuators for more information about attenuator design.
At first glance it was not obvious what was broken inside the device, but there had to be a crack or something between the left connector terminal and the substrate in the photo above. If one looks carefully, it is possible to see that there is indeed a crack around the solder joint. The photo below shows a zoomed in version where the microscopic crack is quite visible.
A possible cause for the crack is if an SMA connector with a misaligned center pin was forced into the connector of the attenuator, causing the terminal to be pushed harder towards the substrate than what it could handle.
So, this is what Mini-Circuits’ low-cost 1 W attenuators look like inside. A custom machined brass body onto which a substrate with laser trimmed resistors are mounted.
I am taking care of a number of Sportident units, which are used in the sport of orienteering. These are small embedded systems powered by non-rechargeable lithium batteries, specifically thionyl chloride (Li-SOCl2) batteries, and every few years the batteries need to be replaced, depending on how much the unit has been used. The units themselves tries to keep track of the battery status by dead reckoning and by measuring the battery voltage, apparently while it is doing something that consumes current. The state of the battery voltage under load can be read out as can the value of the estimated remaining capacity.
To help in determining the status of such batteries, I wanted to have a device that could measure the voltage and the internal resistance in a convenient manner. I had a Teensy laying around and since it has a DAC output and several analog inputs, it looked like a good platform to quickly hook something together that could do the task.
This is the schematics I came up with:
The circuit works like this:
R1 and R2 forms a voltage divider that reduces the battery voltage to below 3.3 V which is the limit of the ADC of the Teensy. Q1 and Rs forms a current sink controlled by the voltage on the A14/DAC pin. Basically the DAC pin sets the base voltage and since the base-emitter voltage is fairly constant, a constant voltage will develop over the emitter resistor Rs. To maintain this voltage Q1 will conduct as much current as required from the battery. The emitter current can be measured by measuring the voltage drop across Rs using analog input A1.
The purpose of having A2 and A3 connected across the base resistor is to be able to measure the (small) base current so that it can be subtracted from the emitter current when calculating the battery current. This is only a small correction and not really important, but since the inputs were available and it was easy to do, I added this little feature.
I built the physical circuit on perfboard and it looks like this:
As can be seen, the whole thing is very simple to build as the Teensy does all the heavy lifting.
I did of course need a program to control the whole thing and do all the measuring, calculations and presentation of results. This is the program I came up with:
/* Lithium battery tester
Tests the internal resistance of a small 3.6 V lithium battery by ramping up the load current and measuring
the pole voltage and calculating the internal resistance.
Written by Per Magnusson, http://www.axotron.se
v 0.1 2015-05-24
This program is public domain.
*/
const float Rtop = 1817; // Top resistor of divider, ohms
const float Rbot = 8170; // Bottom resistor of divider, ohms
const float Rs = 32.8; // Current sense resistor, ohms
const float Rb = 995; // Base resistor, ohms
const float Vref = 3.3; // ADC reference voltage, volts
const int ADCbits = 12;
const int DACbits = 12;
const float voltPerADC = Vref/((1<<ADCbits) - 1.0); // Factor to convert ADC codes to volts
const float voltPerDAC = Vref/((1<<DACbits) - 1.0); // Factor to convert DAC codes to volts
const float DACperVolt = 1/voltPerDAC; // Factor to convert volts to DAC codes
const int detectLimit = 0.8/voltPerADC; // Limit for detecting battery
const float curLim = 35.0e-3; // Maximum test current in A
const float curStep = 5.0e-3; // Target current step
const int maxIter = 3; // Number of iterations to reach target current
const int vsensePin = A0; // Voltage sense pin
const int curSensePin = A1; // Emitter current sense pin
const int baseSenseHiPin = A2; // High base current sense pin
const int baseSenseLoPin = A3; // Low base current sense pin
const int dacPin = A14; // Current control pin
const int ledPin = 13; // LED for debug
const byte sWaitNoBat = 0;
const byte sWaitBat = 1;
byte state;
void setup() {
Serial.begin(57600);
analogWriteResolution(DACbits);
analogReadResolution(ADCbits);
analogWrite(dacPin, 0);
pinMode(ledPin, OUTPUT);
state = sWaitNoBat;
Serial.println("Battery tester");
digitalWrite(ledPin, HIGH); // turn LED on
delay(3000);
digitalWrite(ledPin, LOW); // turn LED off
Serial.println("Waiting for a battery to be connected...");
}
void loop() {
int voltCode;
int baseVoltCode;
int curCode;
int dacVal;
int dacStep;
float volt;
float voltNoLoad;
float curNoLoad;
float cur;
float baseCur;
float res;
float prevCur;
float targCur;
float stepCur;
byte testBat;
byte iter;
analogWrite(dacPin, 0); // Make sure we are not loading the battery in this state
testBat = false;
voltCode = analogRead(vsensePin); // Read battery voltage to see if it is connected
if(state == sWaitNoBat) {
// We are waiting for at battery to be connected
if(voltCode > detectLimit) {
// A battery was connected
testBat = true; // Proceed to test it
}
} else if(state == sWaitBat) {
// We are waiting for a battery to be disconnected
if(voltCode < detectLimit) {
// A battery was disconnected
Serial.println("\nWaiting for a battery to be connected...");
delay(1000); // Delay to not react on glitches while the battery is being disconnected
state = sWaitNoBat;
}
}
if(!testBat) {
// Not in a situation that a battery should be tested
return;
}
// Test the battery
Serial.println("Battery connected, waiting for connection to stabilize.");
delay(1000); // Wait for the connection to stabilize
voltCode = analogRead(vsensePin);
if(voltCode < detectLimit) {
// The battery is gone, it was just a glitch
state = sWaitNoBat;
Serial.println("Battery removed, aborting.");
Serial.println("Waiting for a battery to be connected...");
return;
}
digitalWrite(ledPin, HIGH); // turn LED on
Serial.println("Testing battery.");
voltNoLoad = voltCode * voltPerADC * (Rtop+Rbot)/Rbot;
volt = voltNoLoad;
curNoLoad = voltNoLoad/(Rtop+Rbot); // "No load" current
Serial.println("");
Serial.print("Unloaded voltage: ");
Serial.print(voltNoLoad);
Serial.print(" V (current = ");
Serial.print(curNoLoad*1000);
Serial.println(" mA)");
// Ramp up the current
cur = 0;
targCur = 0;
dacVal = 0.66*DACperVolt; // Base drive starting value, 0.66 V, low current
dacStep = 5.0e-3*Rs*DACperVolt; // Increment ~5 mA per iteration
prevCur = 0;
iter = maxIter; // First step is to read whatever current the starting DAC value results in
// Loop to set a number of different battery test load currents and measure the battery performance at each current
while(1) {
if(targCur > curLim) {
// We are beyond the maximum target current, normal exit from loop
break;
}
if(dacVal >= (1<<DACbits)) {
// The DAC value is too big, exit from loop
Serial.print("Warning: Above maximum DAC setting (");
Serial.print(dacVal);
Serial.println("), exiting");
break;
}
analogWrite(dacPin, dacVal); // Drive the base of the transistor
delay(10);
voltCode = analogRead(vsensePin); // Battery voltage reading
volt = voltCode * voltPerADC * (Rtop+Rbot)/Rbot; // Calculate battery voltage
baseVoltCode = analogRead(baseSenseHiPin) - analogRead(baseSenseLoPin); // Read voltage drop across base resistor
baseCur = baseVoltCode * voltPerADC/Rb; // Calculate base current
curCode = analogRead(curSensePin); // Emitter current reading
// Calculate battery current and compensate for base current and divider current
cur = curCode * voltPerADC/Rs - baseCur + curNoLoad;
if(voltCode < detectLimit) {
// The voltage is too big, exit from loop
Serial.print("Warning: Below minimum battery voltage (");
Serial.print(volt);
Serial.println(" V), exiting");
break;
}
if((cur - curNoLoad) > 0) {
res = (voltNoLoad - volt)/(cur - curNoLoad); // Calculate internal resistance
} else {
res = 0; // Avoid dividing by zero
}
if(iter < maxIter) {
// Make a small adjustment to get closer to the target current
if(cur != prevCur) {
dacVal += dacStep*((targCur-cur)/(cur-prevCur));
}
iter += 1;
} else {
// Print result
Serial.print("Voltage: ");
Serial.print(volt);
Serial.print(" V");
Serial.print(" Current: ");
Serial.print(cur*1000);
Serial.print(" mA");
Serial.print(" Resistance: ");
Serial.print(res);
Serial.println(" ohms");
// Move to next target current
targCur += curStep;
if(prevCur > 0 && (cur-prevCur > 0)) {
// Estimate the step size required to reach the next target current
dacStep = dacStep*((targCur-cur)/(cur-prevCur));
}
dacVal += dacStep;
prevCur = cur;
iter = 0;
}
if(cur > curLim*1.2) {
// The current is too big, exit from loop
Serial.print("Warning: Maximum current exceeded (");
Serial.print(cur);
Serial.println(" mA), exiting");
break;
}
}
analogWrite(dacPin, 0); // Stop the battery current drain
digitalWrite(ledPin, LOW); // turn LED off
state = sWaitBat;
Serial.println("Done");
Serial.println("Disconnect battery.");
}
The program sends information to a serial terminal (I used the one inside the Arduino development environment). It waits for a battery to be connected and then ramps up the current and reports the pole voltage as well as the internal resistance at a couple of different load currents. This is what the output can look like:
Waiting for a battery to be connected...
Battery connected, waiting for connection to stabilize.
Testing battery.
Unloaded voltage: 3.67 V (current = 0.37 mA)
Voltage: 3.63 V Current: 1.39 mA Resistance: 39.36 ohms
Voltage: 3.47 V Current: 4.97 mA Resistance: 41.95 ohms
Voltage: 3.27 V Current: 9.99 mA Resistance: 41.26 ohms
Voltage: 3.08 V Current: 15.03 mA Resistance: 40.23 ohms
Voltage: 2.89 V Current: 19.98 mA Resistance: 39.33 ohms
Voltage: 2.72 V Current: 24.97 mA Resistance: 38.56 ohms
Voltage: 2.54 V Current: 29.99 mA Resistance: 37.88 ohms
Voltage: 2.37 V Current: 34.99 mA Resistance: 37.33 ohms
Done
Disconnect battery.
With a different program, the circuitry can of course also be used to test batteries in different ways.
Update on 2015-10-11
As requested by Alex in the comments, here is a picture of the bottom side of the board (and the corresponding picture of the top).
This post describes how to compile and run ATLC under Cygwin on Windows.
ATLC is an open source 2D transmission line field solver. This means that it can calculate the characteristic impedance and some other parameters of arbitrary single-ended and differential transmission lines. It was written by radio amateur Dr. David Kirkby (G8WRB) and it is very useful when designing RF and high-speed PCBs since it allows accurate calculation of the impedance of pretty much any geometry of transmission line. It is not limited to the simplified geometries that the various closed form approximation formulas deal with, so you can easily (well) include e.g. the effect of the solder mask on top of microstrips or the different permittivity of the resin that is pressed in between the traces of a tightly coupled differential stripline.
ATLC is however not a program with a graphical user interface. Instead it is a command line program written for Unix-like environments that you need to compile yourself from the source code and run from the command line. This creates a perhaps daunting threshold for non-Linux/Unix users. The intention of this post is to describe how to anyway make ATLC work on Windows computers by compiling it under the Linux-like environment Cygwin.
Installing Cygwin
The first step is to download and install Cygwin. This is a two-step process. First you download the setup-executable, either setup-x86_64.exe (64-bit installation) or setup-x86.exe (32-bit installation) from the Cygwin home page.
Then you run the executable. There are a series of questions to answer during the installation. Typically it is best to select to “Install from Internet”, to set the root directory to C:\cygwin64 (assuming a 64-bit installation), select some reasonable folder to store the installation files (perhaps the same place where you saved the setup executable) and select whether or not you are using a proxy to connect to the Internet. Then you come to the first less obvious choice; which download server to use. A good idea might be to try someone that you think is close to home and has decent bandwidth. Then the fun begins, namely the process to select which packages of Cygwin to install. There are many. Very many.
To compile ATLC you need at least the following packages: Devel/gcc-core and Devel/make. I would also recommend Graphics/netpbm since it is a package that can be useful for manipulating images that form the input and output data of ATLC. If you later find out that you want to add more packages, you just rerun the install file and it will remember what you have previously installed and allow you to install more packages.
Downloading and Compiling ATLC
Now you need to download the source files for ATLC. They are available on Sourceforge, http://sourceforge.net/projects/atlc/. To get the source code you need to click on Files, then on atlc (do not click on the link to the Windows binaries as they currently are for an outdated version of ATLC which does not produce correct results in many cases), then click on the most recent version (currently atlc-4.6.1), finally click on atlc-4.6.1.tar.bz2 and wait for the mandatory Sourceforge download delay to expire. There is also a .tar.gz package of source files, but that archive seemed to be broken when I tried to unpack it, while the bz2 archive was OK.
Save the source package in some directory (I chose D:\download\ATLC) and then unpack it using e.g. 7-zip, first the bz2 level and then the tar level to get a directory tree of all the source files. You may want to move the folder that contains the proper top level folder up to the same level as the bz2 file. Now we have the folder D:\download\ATLC\atlc-4.6.1 with the top level of the source tree.
Start Cygwin and change directory to the source files. Cygwin maps the Windows drive letters to folders called /cygdrive/<driveletter>, so to change to the desired folder, you need to type in the following command:
cd /cygdrive/d/download/ATLC/atlc-4.6.1
To prepare for building the exe file, type:
./configure
This performs some checks on the target system (your Windows PC) and creates a suitable makefile. Hopefully every check goes well (it did for me) so then it is time to build the whole thing by typing:
make
This takes a little while, but if everything goes well, the result are some .exe files in the src directory, the most important of which is atlc.exe. It is then a good idea to also run an automatic test to see if everything went well:
make check
The last lines of output when I run the check are:
Run times: T_sequential is 26 s. Not configured for parallel operation.
PASS: benchmark.test
======================
All 82 tests passed
(2 tests were not run)
======================
This looks good to me.
Then we can “install” ATLC, which mostly means copying files to suitable locations. This is done by:
make install
Trying It Out
Now, cd to a directory where you want to work with ATLC. You can type:
man atlc
to get some information about how the program works. You can also read about it on the ATLC page, primarily under the headings Tutorial, Bitmaps, Man pages and Examples.
Use Paint, Gimp, Photoshop, some other drawing programs, a script or one of the other .exe files that were compiled along with atlc.exe to create suitable input data to ATLC. Basically, the input data consists of a 24-bit BMP image which represents the cross section of the transmission line. Specific colors represent different materials as described in the man page. Red (0xFF0000) represents the conductor of a line, green (0x00FF00) is the ground conductor, white is vacuum (a good enough approximation of air) some other colors represent predefined dielectrics while most colors are free to use as custom dielectrics with permittivity that can be defined on the command line.
Below is a (scaled down) example picture I created. It represents a 0.52 mm wide microstrip line without solder mask on a 0.3 mm thick FR-4 substrate.
I have put a ground plane not only under the FR-4, but also all around the edges of the image. This helps with ATLC convergence. Such a boundary ground plane should not be placed too close to the actual transmission line in order not to affect the impedance significantly. I chose to use a scale of 200 pixels per mm in this case, but you do not need to tell ATLC that since the impedance is scale invariant. Using more pixels per mm gives a more accurate result.
To feed this picture to ATLC, the following command can be issued:
atlc -d d2ff00=4.2 052_030_microstrip.bmp
The option “-d d2ff00=4.2” tells ATLC that the color 0xd2ff00 represents a dielectric with a relative permittivity of 4.2. The other colors that were used (white, green and red) have predefined properties.
The output data from ATLC (after a few seconds) is:
This means that ATLC has calculated that the effective permittivity of the line is 3.03, the characteristic impedance is 51.457 ohms, the capacitance is 112.9 pF/m, the inductance is 298.8 nH/m, the propagation velocity is 172.2*106 m/s, which is 0.574 times the speed of light in free space.
A number of output files are generated as well, both BMP-images that represent the electrical and magnetic fields and binary files that represent the same thing, but with higher accuracy. See the Files section on the ATLC home page for descriptions of these files.
As mentioned above, a higher resolution (more pixels per mm) results in more accurate results, but the downside is that the runtime increases steeply. I seem to remember (cannot find it right now) that the run time of ATLC is roughly proportional to the square of the number of pixels in the input image, so doubling the resolution (in pixels per mm) creates four times as many pixels and hence 16 times as long execution time.