Advertisements

Posts Tagged Arduino

Generic I²C 128×64 OLED Displays: Beware Swapped VCC and GND

A batch of 1.3 inch white I²C OLED displays arrived from halfway around the planet, so I figured I could run a quick acceptance test by popping them into the socket on the crystal tester proto board:

White 1.3 inch OLED on crystal tester

White 1.3 inch OLED on crystal tester

The first one flat-out didn’t work, as in not at all. The original display continued to work fine, so I compared the old & new displays:

OLED Modules - pinout difference

OLED Modules – pinout difference

Yup, swapped VCC and GND pins. I should be used to that by now.

I rewired the socket, tried the new displays, undid the change, popped the original display in place, and all is right with the world. Somewhat to my surprise, all five new displays worked, including the one I’d insulted with reversed power.

Advertisements

,

12 Comments

Teledyne 732TN-5 Relay: Zowie!

The first pass at the crystal tester used a manual jumper to switch the 33 pF series capacitor in / out of the circuit:

Quartz crystal resonance test fixture

Quartz crystal resonance test fixture

With an Arduino close at hand, however, a relay makes somewhat more sense. For long-forgotten reasons, I have a small fortune in Teledyne 732TN-5 relays intended for RF switching:

Teledyne 732TN-5 Relay

Teledyne 732TN-5 Relay

The 7820 date code on the side suggests they’ve been in the heap basically forever, although some fractions of Teledyne still exist and you can apparently buy the same relay today at 50 bucks a pop. It’s definitely overqualified for this job and you can surely get away with an ordinary DIP DPDT (or, heck, even SPST) relay.

It seems I picked a hyper-bright white LED: the red ink tones it down a bit. Black might be more effective. A diffused LED may be in order.

The “TN” suffix indicates a built-in transistor driver with a catch diode on the relay coil, so the relay needs power, ground, and a current drive into the transistor’s base terminal:

Teledyne 732TN relay - drive schematic

Teledyne 732TN relay – drive schematic

Even with the internal catch diode, I ran the +5 V power through a 12 Ω resistor to a 10 µF cap in hopes of isolating the inevitable switching transients from the DDS and log amp. As a result, the turn-on transient isn’t much of a transient at all:

Teledyne 732TN Relay - turn-on transient

Teledyne 732TN Relay – turn-on transient

The 560 mV drop suggests a 47 mA coil current through the 12 Ω resistor, just about spot on for a 100 Ω coil.

The energy stored in the coil makes the turn-off transient much steeper:

Teledyne 732TN Relay - turn-off transient

Teledyne 732TN Relay – turn-off transient

Note the 1.5 µs delay from the falling control input to the relay opening. Granted, it’s running at 4.7 V, not the rated 5 V, but that’s still rather peppy. The turn-on delay seems to be about the same, making the datasheet’s “6 ms nominal” operating time look rather conservative.

Dang, that’s a nice gadget!

,

1 Comment

128×64 OLED Display: I²C Timings

The OLED display has a noticeable delay between writing the first (double-size) line of text and the last line, which seemed odd:

White 128x64 OLED Display - crystal tester

White 128×64 OLED Display – crystal tester

The top trace in this scope shot goes high while the code begins the display update, which involves converting the variable to strings, the characters to bitmaps, then writing the data to the display:

IIC Timing - overall

IIC Timing – overall

The bottom trace shows I²C bus activity pretty much blots up all the time, with very little required for the computations in between the display writes for each text line.

Near the leading edge of the top trace, the code computes the new delta phase value and the X axis DAC output corresponding to that frequency:

TestCount.fx_64 = MultiplyFixedPt(ScanFreq,CtPerHz); // compute DDS delta phase
TestCount.fx_32.low = 0; // truncate count to integer
TestFreq.fx_64 = MultiplyFixedPt(TestCount,HzPerCt); // compute actual frequency

Temp.fx_64 = (DAC_MAX * (ScanFreq.fx_64 - ScanFrom.fx_64)) / ScanWidth.fx_32.high;
XAxisValue = Temp.fx_32.high;

WriteDDS(TestCount.fx_32.high); // set DDS to new frequency
XAxisDAC.setVoltage(XAxisValue,DAC_WR); // and set X axis to match

The burst in the top trace shows the five SPI writes to the DDS (one pulse per byte, with the hardware handling the serialization) and the bottom trace shows four I²C bus writes to the DAC:

IIC Timing - DDS to SPI - IIC to DAC

IIC Timing – DDS to SPI – IIC to DAC

A bit more detail shows writing each I²C byte to the DAC requires nine clock pulses (8 data, 1 ack):

IIC Timing - DDS to SPI - IIC to DAC detail

IIC Timing – DDS to SPI – IIC to DAC detail

The I²C bus ticks along at 400 kHz, with each byte requiring 33.4 µs (including the mandatory downtime around each burst), so the DAC update requires about 100 µs. The MCP4725 datasheet suggests a three byte “fast mode” write, but there’s not much point in doing so for my simple needs.

The display ticks along at the same pace with far more data.

In round numbers, the entire display update hits 6 text lines (1 double-height + 4 single-height) × 16 characters / line × 64 pixels / character = 6144 pixels.

The first scope shot shows the update requires something close to 90 ms, which allows for 2700 bytes = 90 ms / 33.4 µs, the equivalent of 21 k pixels. The SH1106 hardware includes an internal address counter, so there’s no need to transfer an address with each byte; I’m not sure where the factor-of-two overhead goes.

In order to get a faster update, there’s a definite need for lazy screen updates: no writes when there’s no change.

This probably doesn’t matter, because I can’t watch much faster, but it’s good to know the fancy fixed-point arithmetic isn’t the limiting factor.

1 Comment

AD8310 Log Amp Module: Sidesaddle Bracket

This little bracket attaches to a proto board holder, with holes for M3 inserts to mount the AD8310 log amp module:

PCB Side Bracket - 80x120

PCB Side Bracket – 80×120

Thusly:

AD8310 module bracket on proto board holder - component side

AD8310 module bracket on proto board holder – component side

The OLED display looks a bit faded, which seems to be an interaction between matrix refresh and camera shutter: looks just fine in person!

Not much to see from the other side:

AD8310 module bracket on proto board holder - solder side

AD8310 module bracket on proto board holder – solder side

I should have included an offset to slide it a bit forward; then I could mount it on the other end with clearance for the Nano’s USB port. Maybe next time.

The OpenSCAD source code as a GitHub Gist:

, ,

Leave a comment

AD9850 DDS Module: 1.3 inch I²C OLED FTW

A white 1.3 inch I²C OLED turns out to be much more readable than the yellow-blue 0.96 inch version:

Arduino with OLED - white 1.3 inch

Arduino with OLED – white 1.3 inch

Of course, after you make it readable, you immediately make room to cram more data on it:

White 1.3 inch OLED on crystal tester

White 1.3 inch OLED on crystal tester

That’s on the proto board with the Arduino and AD9850 DDS ticking away on the left; the bright red MCP4725 DAC will eventually drive the scope’s X axis. Shifting the display to the I²C interface and cleaning up my SPI initialization code worked wonders: the DDS now steps a sine wave at 0.1 Hz (pretty nearly) intervals from 57.0 to 60.3 Hz.

, ,

5 Comments

AD9850 DDS Module: OLED Display

Those little OLED displays might just work:

Arduino with OLED - simulated DDS

Arduino with OLED – simulated DDS

The U8X8 driver produces those double-size bitmap characters; the default 8×8 matrix seem pretty much unreadable on a 0.96 inch OLED at any practical distance from a benchtop instrument. They might be workable on a 1.3 inch white OLED, minus the attractive yellow highlight for the frequency in the top line.

The OLED uses an SPI interface, although the U8X8 library clobbers my (simpleminded) SPI configuration for the AD9850 DDS and I’ve dummied out the DDS outputs. A soon-to-arrive I²C OLED should resolve that problem; changing the interface from SPI to I²C involves changing the single line of code constructing the driver object, so It Should Just Work.

The U8X8 driver writes directly to the display, thus eliminating the need for a backing buffer in the Arduino’s painfully limited RAM. I think the library hauls in all possible fonts to support font selection at runtime, even though I need at most two fonts, so it may be worthwhile to hack the unneeded ones from the library (or figure out if I misunderstand the situation and the Flash image includes only the fonts actually used). Each font occupies anywhere from 200 to 2000 bytes, which I’d rather have available for program code. Chopping out unused functions would certainly be less useful.

The display formatting is a crude hack just to see what the numbers look like:

    int ln = 0;
    u8x8.draw2x2String(0,ln,Buffer);
    ln += 2;

    TestFreq.fx_64 = ScanTo.fx_64 - ScanFrom.fx_64;
    PrintFixedPtRounded(Buffer,TestFreq,1);
    u8x8.draw2x2String(0,ln,"W       ");
    u8x8.draw2x2String(2*(8-strlen(Buffer)),ln,Buffer);
    ln += 2;

    PrintFixedPtRounded(Buffer,ScanStep,3);
    u8x8.draw2x2String(0,ln,"S       ");
    u8x8.draw2x2String(2*(8-strlen(Buffer)),ln,Buffer);
    ln += 2;

    TestFreq.fx_32.high = SCAN_SETTLE;                    // milliseconds
    TestFreq.fx_32.low = 0;
    TestFreq.fx_64 /= KILO;                               // to seconds
    PrintFixedPtRounded(Buffer,TestFreq,3);
    u8x8.draw2x2String(0,ln,"T       ");
    u8x8.draw2x2String(2*(8-strlen(Buffer)),ln,Buffer);
    ln += 2;

Updating the display produces a noticeable and annoying flicker, which isn’t too surprising, so each value should have an “update me” flag to avoid gratuitous writes. Abstracting the display formatting into a table-driven routine might be appropriate, when I need more than one layout, but sheesh.

I calculate the actual frequency from the 32 bit integer delta phase word written to the DDS, rather than use the achingly precise fixed point value, so a tidy 0.100 Hz frequency step doesn’t produce neat results. Instead, the displayed value will be within ±0.0291 Hz (the frequency resolution) of the desired frequency, which probably makes more sense for the very narrow bandwidths involved in a quartz crystal test gadget.

Computing the frequency step size makes heavy use of 64 bit integers:

//  ScanStep.fx_64 = One.fx_64 / 4;                       // 0.25 Hz = 8 or 9 tuning register steps
  ScanStep.fx_64 = One.fx_64 / 10;                    // 0.1 Hz = 3 or 4 tuning register steps
//  ScanStep.fx_64 = One.fx_64 / 20;                    // 0.05 Hz = 2 or 3 tuning register steps
//  ScanStep = HzPerCt;                                   // smallest possible frequency step

The fixed point numbers resulting from those divisions will be accurate to nine decimal places; good enough for what I need.

The sensible way of handling discrete scan width / step size / settling time options is through menus showing the allowed choices, with joystick / joyswitch navigation & selection, rather than keyboard entry. An analog joystick has the distinct advantage of using two analog inputs, not four digital pins, although the U8X8 driver includes a switch-driven menu handler.

There’s a definite need to log all the values through the serial output for data collection without hand transcription.

The Arduino source code as a GitHub Gist:

, ,

2 Comments

AD9850 DDS Module: Temperature Sensitivity

While tinkering with the SPI code for the AD9850 DDS module, I wrote down the ambient temperature and the frequency tweak required to zero-beat the 10 MHz output with the GPS-locked oscillator. A quick-n-dirty plot summarizing two days of randomly timed observations ensued:

AD9850 DDS Module - Frequency vs Temperature

AD9850 DDS Module – Frequency vs Temperature

The frequency offset comes from the tweak required to zero-beat the output by adjusting the initial oscillator error: a positive tweak produces a smaller count-per-hertz coefficient and reduces the output frequency. As a result, the thermal coefficient sign is backwards, because increasing temperature raises the oscillator frequency and reduces the necessary tweak. I think so, anyway; you know how these things can go wrong. More automation and reliable data would be a nice touch.

Foam sheets formed a block around the DDS module, isolating it from stray air currents and reducing the clock oscillator’s sensitivity:

AD9850 DDS module - foam insulation

AD9850 DDS module – foam insulation

I used the ambient temperature, because the thermocouple inside the foam (not shown in the picture) really wasn’t making good contact with the board, the readings didn’t make consistent sense, and, given a (nearly) constant power dissipation, the (average) oscillator temperature inside the foam should track ambient temperature with a constant offset. I think so, anyway.

The coefficient works out to 0.02 ppm/°C. Of course, the initial frequency offset is something like -400 Hz = 3 ppm, so we’re not dealing with lab-grade instrumentation here.

,

9 Comments