Sunday, December 15, 2013

12VDC -> 220V AC step-up transformers, a.k.a inverters and laptop chargers don't always work together well.

The other day, I had to go on a long road-trip and wanted to make sure I would keep access to my laptop throughout the journey.

Without giving it much thought, I went to the car parts store and bought me one of these:





The idea was to convert the 12V DC output of my battery to a good old 230V AC that I could plug my 220V AC to 20V DC laptop adapter in.

First, it's kind of sad to jump through all these hoops just to do a 12VDC to 20VDC step-up, but I was in a rush.

Second, a bit overkill on the power rating side, but you never know what you'll end up needing to hook to your car battery when on a camping trip.

Well ... turns out buying this things was a bad idea: contrary to expectations, the laptop refused to charge.

Back home a couple of days later, I decided to investigate ...

First step, let's tear the box open to see what goes on in there:



At first glance,  the inside looks very much like most inverter circuit schematics you cand find on the net:
  • A DC input stage on the right and top with a bunch of what's likely foolproofing diodes and noise filtering caps.
  • A good old LM324N quad op-amp, maybe used to produce the AC oscillation
  • Two ICs that look like voltage regulators or voltage references, codename KA7500B
  • A bunch of power transistors hooked up to the case which doubles as a heat-sink. Transistors look like they are organized in push-pull configuration to current-amplify the oscillation.
  • A bunch of transformers to do the 12V -> 230V step-up.
  • A big fat 400V-rated electrolytic cap, not sure what for, maybe some more filtering.
  • The 230V AC output stage.
In other words, nothing fancy.

Yet things don't work.

Time too hook the whole thing up to the scope to see what's going on:


Now let's feed it 12VDC on the DC input side (current limited to 1A, you never know):



First thing I notice: the darn thing sucks up 8 Watts of power without any load on the AC side :-(
Don't let it hooked to your car battery with the engine off.

Anyhoo, continuing, Fluke says 12V are going in, everything looks good.


And let's take a gander a what comes out the other side:



Ahah.

And ugh.

So, sure enough, at first glance, we get "mains-like" juice:
  • 50Hz says the scope
  • 296V plus peak says the scope
  • -300V minus peak says the scope
  • 244V RMS says the scope (close enough to 230V)
But but but ... the likeness stops there: the waveform is not exactly what I'd call a nice sine wave ... it's in fact an awful bad PWM-like approximation of a sine wave. Yuck.

This is very likely where the problem comes from: I bet my laptop power adapter sorta expects a real sinusodial waveform coming in.

This half-assed approximation of a sine wave is probably making it choke and shut down.

Solution ?

I think there are a few possible avenues:
  • Hacking the beast above by adding a large filtering cap after the output. Not confident enough in my electronics skills at this stage, so not going to try it.
  • Buying a "pure sine wave" adapter. I guess that's what I should have done, had I thought of doing my research :(
  • Buying a DC-to-DC laptop adapter which will at any rate likely be more efficient and more silent (hopefully no large fan and heatsink). Probably something like that.
At any rate, caveat emptor:

If you plan to charge your laptop in your car with a power inverter, you had better triple-check that the so-called "AC output" of the inverter you plan to buy is actually compatible with the input expected by your laptop adapter.

Best way to do this ? Confirm with salesman, buy it, try it, bring back to salesman if it doesn't charge your laptop, iterate.

But don't do what I did and expect it to work simply because the inverter says "230V AC output" and the laptop adapter says "230V AC input".

Sunday, November 17, 2013

Arduino and DAC

My quest to implement a version of the analogWrite() function on the Arduino that actually behaves as the name advertises continues.

In my previous post, I built a 4-stage Sallen-Key low pass filter to convert the PWM output to a flat signal, but it had shortcomings:
  • Lots of components to get a flat signal
  • Only 8 bit of resolution means only 256 voltage levels (although this could probably be improved using PWM tricks).
  • Unbuffered output meant I'd have to add a couple of op-amps behind it to get it to behave like a stable voltage source.
Today, I am trying to do the same thing with a real digital to analogue converter (a DAC) : the Analog Device DAC8412FPZ.

I just got my hands on one of these chip, and this is what it looks like:


Here's the data sheet for this little guy.

Advantage of this chip:
  • 12 bit output resolution gives you 4096 possible output voltage levels.
  • 28-pin DIP packaging makes for easy breadboarding and experimenting.
  • 4 channels (effectively, the chip contains 4 quasi independent DACs) so you can send 4 independent analogue signals to 4 separate devices.
  • Works with both unipolar or bipolar power supplies in [5V, 30V] range means you can power it from an Arduino +5V supply for experiments but still move the final project to a proper bipolar +/- 12V supply later on.
  • Outputs are buffered and can source or sink +/- 5 mA at 10V, which means you don't have to add a power amplification stage after the DAC if all you want is to drive small loads.
  • Data transfers to the chip is done with a parallel bus, which is annoying to wire up, but super simple to manipulate in software.
Disadvantages of this chip:
  • Chip is kinda bulky (4cm x 1.5cm)
  • Unless you manage to get your hands on a sample, chip is expensive (~ $35)
  • The parallel bus is a bit of a pain to wire up (12 wires) and you need a lot of pins on the micro controller unit side. 
This last part is actually the most annoying: you are going to need an Arduino Mega if you want enough digital ports to control this thing directly.

Here's  what it looks like all wired up on the breadboard:



A couple of things to note:
  • The big bundle of green wires on pin 30 to 41 of the Arduino (12 of them) is the data bus and it's the reason why you need a Mega to makes this work :(.
  • The two blue wire are the "address bus", that you use to select which of the 4 DACs you're fiddling with.
  • In case you're wondering what they're for, some patch cables are only in there to mechanically anchor the Mega to the breadboard.
  • The Arduino's digital pins 7, 6, 5, 4, 3, 2 are used to manipulate the control pins of the DAC.
  • The Arduino, via its A0 analog pin, is also used to read back the voltage output of the DAC to check that it all works (we only get a resolution of 1024 levels since the embedded ADC of the Arduino only does 10bits).
  • In the pictures, the DAC is powered by an external +/- 12V power supply. For this, I use this excellent little breakout board from the good folks at dangerous prototypes which neatly converts the output of an old PC ATX power supply I had lying around.
  • In this example, the VRefHigh and VRefLow pins of the DAC are hooked respectively to the +5V and GND of the Arduino, which means the DAC will output 4096 levels in the [0, 5V] interval. This makes it easy to read back the DAC's output voltage directly with the Arduino. However, you could hook these two pins to any other external reference (as long as VRefLow is less than VRefHigh and they're smaller than the power voltages), including making VRefLow negative to get the DAC to crank out negative voltages.
On the software side, here is a simple Arduino sketch that will:
  • Setup the Arduino pins to control the DAC
  • Reset the DAC
  • Control the DAC to repeatedly output all possible values in [VRefLow, VRefHigh] on all 4 channels at once
  • Use pin A0 to read back one of the DAC's voltage output
  • Dump the value read on the serial port.

Here is the code:

 // DAC8412FPZ  
   
 #define ADDR_BIT0 7 // Control pin got LSB of address bus  
 #define ADDR_BIT1 6 // Control pin got MSB of address bus  
 #define DATA_BIT0 30 // Control pin for LSB of data bus  
   
 #define RW 5 // Pin for read-write command : High -> read, Low -> write  
 #define CSB 4 // Pin for chip select : High -> Off, Low -> On. Chip ignores commands unless this is low  
 #define RSTB 3 // Pin to Reset all channels to midscale : High -> do nothing, Low -> do reset  
 #define LDACB 2 // Ping to control "Load DAC"  
   
 #define analogPin A0  
 static uint32_t count = 0;  
   
 // Prep the address bus to talk to one DAC  
 static inline void setAddrBus(  
   int dacId  
 )  
 {  
   digitalWrite(ADDR_BIT0, (dacId & 0x1) ? HIGH : LOW);  
   digitalWrite(ADDR_BIT1, (dacId >> 0x1) ? HIGH : LOW);  
 }  
   
 // Prep the data bus with a given value  
 static inline void setDataBus(  
   int16_t value  
 )  
 {  
   for(int i=0; i<12; ++i) {  
     digitalWrite(  
       i + DATA_BIT0,  
       (value & (1<<i)) ? HIGH : LOW  
     );  
   }  
 }  
   
 // Manipulate the "chip select" signal  
 static inline void ioState(  
   bool on  
 )  
 {  
   digitalWrite(CSB, on ? LOW : HIGH);  
 }  
   
 // Load a value on all 4 DACs  
 static inline void loadValue(  
   int16_t value  
 )  
 {  
   for(int i=0; i<4; ++i) {  
     ioState(false); delay(10);  
       setAddrBus(i); delay(10);  
       setDataBus(value); delay(10);  
     ioState(true); delay(10);  
     ioState(false); delay(10);  
   }  
 }  
   
 // Read a value from A0, output on serial  
 static inline void output()  
 {  
   uint16_t value = analogRead(analogPin);  
   Serial.print("count:");  
   Serial.print(count);  
   Serial.print(" millis:");  
   Serial.print(millis());  
   Serial.print(" value:");  
   Serial.println(value);  
 }  
   
 void setup()  
 {  
   // Prep control pins  
   pinMode( RW, OUTPUT);  
   pinMode( CSB, OUTPUT);  
   pinMode( RSTB, OUTPUT);  
   pinMode( LDACB, OUTPUT);  
   pinMode(ADDR_BIT0, OUTPUT);  
   pinMode(ADDR_BIT1, OUTPUT);  
   for(int i=0; i<12; ++i) {  
     pinMode(i + DATA_BIT0, OUTPUT);  
   }  
   
   // Reset DAC  
   digitalWrite(RSTB, LOW); delay(10); // Turn reset on  
   digitalWrite(RSTB, HIGH); delay(10); // Turn reset off  
   
   // Prep serial for output  
   Serial.begin(230400);  
 }  
   
 void loop()  
 {  
   loadValue(count*10);  
   output();  
   ++count;  
 }  
Here is the code on github.
And finally, here's the oscilloscope output:
Looks neat :)

Saturday, November 9, 2013

Active low-pass filtering of Arduino's pwm output for a clean, flat, software adjustable signal.

The rather ill-named Arduino's analogWrite() function absolutely does not do what its name suggests: you'd expect it to set the voltage one of the output pins to the given value.

To do this you'd typically need something called a "digital to analog converter", or DAC in short, something the Arduino unfortunately does not come equipped with.

Instead, analogWrite() generates a PWM signal,  i.e. a square wave with "ON" and "OFF" timings designed such that the *average* voltage value is what you requested (the so-called duty cycle of the signal).

This is fine to - say - drive a motor, but certainly doesn't help if you want a clean, flat, noiseless signal.

In fact, the whole premise of PWM is the assumption that the loads it's ever going to drive have a built-in filter. In the case of a motor, the slow response times of the physical device is the filter in question.

But for some applications, PWM just does not cut it: sometimes, you'd like your Arduino to produce a clean, flat, noiseless constant voltage signal that can be controlled in software.

The standard RC low pass filter (such as the one described in this article) kinda works for this and is super easy to build, but it generates a fairly noisy output signal.

For example, below is the standard (490Hz / 50% duty cycle) PWM output of the Arduino fed through such a RC low pass filter:




Bottom line, the output is better than the PWM wave, but still awful choppy.

A larger cap, or maybe a multi-stage RC low-pass filter would of course help smooth out the noise further, but:
  • you might not have larger caps handy
  • you'll still get fairly fat ripple
Here's for example a 4 stage RC low-pass filter.
The output still has ripples:


Looking at the components I have lying around, I tried to put something together to produce a flatter, software adjustable voltage level from my Arduino.

Turns out I had a LM324N quad op-amp, a bunch of caps and resistors, generally enough to put together a four stage active low pass filter, with each stage based on the Sallen-Key Low Pass topology.

I didn't really spend much time doing theoretical analysis of the circuit to figure our caps values, but rather played with what I had lying around until I found a combo that worked well with the signal produced but the Arduino.

Here's what the circuit looks like:



Note: LTSpice does not come standard with models for the LM324, and if you want to play with the circuit above, you'll need to add two files to your LTSpice lib directory (mine happens to be in ~/.wine/drive_c/Program Files (x86)/LTC/LTspiceIV/lib/sym/Opamps , but YMMV).

Put this SYM file in lib/sym/OpAmps
Put this MOD file in lib/sub

This last circuit has a number of advantages:
  • The output is a lot cleaner than the RC filter, and cleanly maps the [0 - 255] range of the analogWrite() function to the full [0-5V] range.
  • Because it is a 12V powered, active circuit, it can easily be hacked  to produce 255 clean levels in [0- X volts] where X is the max output voltage of your OpAmps. All that's needed is a gain bridge on the feedback loop of the 4th stage, and if you are using an OpAmp that can goes rail to rail (which the 324N isn't), you can sweep the full 12V range.
  • I've tried to measure the theoretical output noise in LTSpice, and looks like it is in the 10µV range ... well below the noise level generated by my 12V power supply.
Here's what the thing looks like on a breadboard:



Note that:
  • the quad op-amp is the IC in the center
  • each of the stages is laid out vertically around the op-amp
  • I've added a 330Ω + LED load at the output.
Here's an oscilloscope screenshot of the signal output:



Good enough for me.