TAMRON 70 – 300 LENS interface project

TAMRON 70 – 300 Lens test driver, by G8GKU.

This article presents information relating to the interfacing of a TAMRON lens to an Arduino Nano.

Having acquired a well-used TAMRON 70-300 mm A.F. zoom lens, ( Cannon mount ) from a photographer friend, along with the information that the lens now only occasionally operates properly after many years of faultless service, it was decided to attempt to communicate with the lens, as a technical challenge.

Important.  Before proceeding further it has to be made 100% clear that the information presented here relates to a written-off lens with zero financial value. Any use of this project and related information should only be undertaken with the full understanding that interfacing to a lens of financial value may result in financial loss and / or damage to the lens. Be warned.

General Information.  The lens is of the older technology where the auto focus mechanism is driven by a miniature electric motor.

A reader may be tempted to immediately connect the lens to the interface electronics and then to run the code, however,  it is strongly suggested the notes below are read first.

If a fast start is needed then as a very minimum read the paragraphs relating to the wiring to the lens and connect the lens to the electronics only via the recommended resistors as detailed below and ensure the correct voltages are available before running the code.

Information gleaned via the internet indicated the lens communicates with the host camera via a serial protocol, in this case a three wire SPI bus.

Lens electrical connections.  Physical electrical connections between the lens and camera are via contacts arranged in semi-circular layout on the camera-facing mounting ring of the lens.

The lens used has 7 physical connector pads, others may differ.

Details of the individual contacts were quickly found via the internet, a brief
list of the contact pads is below ;

Visually the external lens contacts are distinguished by having a double-width pad placed second from one end of the group, the adjacent single pad is taken as ‘first’ pad in this project.

Taking the first pad, ( as above ) the pads are believed to have functions as listed below ;

Vbat ( +5v ( 6v ? ) power to the lens motor-drive electronics )
Analogue Ground, ( note, this is the double width pad, ground 0v, relating to the motor power )
VDD ( believed to be + 5v )
Data Out ( from camera, into lens )
Data In ( into camera, from lens )
Data Clock ( supplied by camera )
Digital Ground ( ground 0v, for data connection(s) )

N.B. in this interface project the Arduino Nano will provide the signals relating to the ‘ camera ‘  in the list above.

 

Lens ( Motor ) powers supply, ( 5v )

Nominally the lens motor power is supplied to a dedicated lens connector and is separate from the Logic 5v Vcc.  The author supplies the 5v motor supply from a bench power supply set to 5v. It may be possible to use the same 5v as used for the Vcc from the Nano, additional capacitors on the 5v line would be suggested. However, the author has not tested this, only the bench supply being used.

To initially test the overall project, the code will read back the Zoom position without the motor power being supplied, as only the Vcc 5v is needed. This allows a ” gentle start ” to the experiment before the motor 5v is applied for tests requiring it.

 

Interface Wiring.  (  Interface wiring to the lens )
It must be noted that the easiest connection method which is also 100% non invasive to the lens itself, is to use a lens adapter or extender unit, which carries duplicated pins on both sides, fitting between the lens and the camera body.

If available, such an adapter could have the interface wires  fitted in an invasive fashion and as such may be used as a connection platform for the lens interface, thereby not making intrusive change to the lens.

As the above was not an available option and the lens in hand was a zero-cost write-off, the mounting ring and similar parts of the lens were unscrewed and removed, providing access to the inner electronic system parts of the lens. The fixing screws are small scale and are tightly fitted. Use of a clean and un-rounded
screwdriver is recommended, use a firm grip.

After dismantling it will be found that the pads on the outside of the lens are part of a small plastic mounting assembly, held in place by two further small screws, removal of these screws releases the pad assembly. To prevent any chance of the contact pads of the released contact assembly touching other electrical connections, the pad unit was covered with removable insulating tape.

The pad assembly is electrically connected to the internal curved printed circuit board ( PCB ) by means of a short flexible multi-track PCB.

**IMPORTANT**
If the conductors of the above short flexible PCB are inspected it will be seen that the first external pad and the double-size pad are swapped in sequence by the flexible PCB as it terminates to the inner PCB.

Image above shows thin wires soldered to inner curved PCB.

To make the physical wiring interface to the lens, seven thin flexible multi-strand wires were soldered to the inside destination end of the flexible PCB. Each wire being about 50 cm in length. The lens end of each wire being trimmed back to about 1 mm after solder tinning, facilitating small joints. Delicate and careful soldering is required, use a different colour for each wire if possible.

Allowing for the above two-pad reversal, the wires soldered to the inside curved PCB are as below in sequence, starting at the end where the double-width pad terminates to the inner PCB via the flexible PCB ;

Analogue Ground, ( note, this is the ( external ) double width pad, ground 0v, relating to the motor power )
Vbat ( +5v ( 6v ? ) power to the lens motor-drive electronics )
[ items below are in same sequence as per the external pads ] VDD ( believed to be + 5v )
Data Out ( from camera, to lens )
Data In ( into camera, from lens )
Data Clock ( supplied by camera, in our case, by the interface )
Digital Ground ( ground 0v, for data connection(s) )

Due to the operation of the electrical interface within the lens it is important that
resistors are used to allow the lens and the Arduino Nano to interlink and operate without problems which may be caused by code or other errors, also under normal operation.

Do **NOT** directly connect the Nano pins, via wires,  to the LENS electronics.

Please see and use the wiring diagram, as below,  dia 1.

A summary of the wiring diagram is provided below for convenience, resistors in the MOSI and MISO lines are strongly recommended.
The resistors are soldered to the Nano end of the interface wires and heat-shrink insulated.

……  connect Nano MOSI, pin D11, via a 470 Ohm resistor ( 390R to 1k suitable ) to the Dout lens pad.

……  connect Nano MISO, pin D12, via a 470 Ohm resistor ( 390R to 1k suitable ) to the Din lens pad.

Please understand, without resistors in series with pins D11 and D12, it is possible that damage may or will be inflicted upon the lens electronics or the Nano or both if errors occur.

…..  connect Nano D13, logic OUTPUT, Clock signal source, via a 1k resistor to the lens Data Clock input.

…..  connect Nano D8, logic INPUT, pin via a 2k2 resistor to the lens side of the Clock signal from D13

The two resistors for Nano pins D13 and D8 are mandatory.

Explanation.
When in operation, the CPU internal to the lens holds down the
clock signal whilst it is busy, typically post receiving a command.

Connecting the clock drive signal from the Nano to the lens via a resistor, will allow the Nano to drive the clock signal to the lens from Nano pin D13, without either Nano or lens suffering damage when or if the two ends are at opposite logic states, i.e. when the lens holds down the clock signal.

The 2k2 resistor allows the state of the *lens* clock signal( logic 1 or 0 ) to be read back into the Nano via pin D8.

The Code.
There is no intention in the code to provide anything close to a fully specified interface to the lens.
Code provided demonstrates how the interface protocol functions and provides an example which works in conjunction with the lens used by the author.

Functions provided will read the position of the zoom function and will drive the auto focus motor between close in and far out focus, also the Iris can be made to ‘ blink ‘.

Other functions and the related specific command codes are easily found via the internet and interested persons are invited to experiment with the code as they need.

 

code is found here ;

TAMRON

 

 

Dual AD9850 in Quadrature and finer phase adjustment

Project: Adjustable frequency dual AD9850 DDS units with output adjustable phase from 0 to 180 degrees in 11.25 phase increments.

Platform:
-Arduino Uno R3
-2x 9850 DDS units
-Arduino software written in Arduino IDE version    1.6.8
-Computer running serial terminal client software to send commands over serial port

The AD9850 has five bits that control the phase allowing for 11.25 (360/32) degree phase adjustment resolution.

The original code provides a way to adjust frequency while keeping a constant phase difference of 90 degrees.

The modified code gives finer phase adjustment in 11.25 degree increments from 0 to 180 degrees.

The menu of commands is sent to the serial terminal software when the Arduino is powered on:
COMMAND LIST
as      DDS A sine o/p
ac      DDS A cos o/p
bs      DDS B sine o/p
bc      DDS B cos o/p
To adjust phase type b0000, b1125, b…, b16875, b18000
To set DDSB 90 behind DDSA: set as and bc
To set DDSB 90 ahead of DDSA: set as and b18000
To set DDSB 45 ahead of DDSA: set as and b4500
To setup DDSB 45 behind DDSA: setup as and b13500
To setup DDSB 11.25 behind DDSA: setup as and b7875
To setup DDSB 11.25 ahead of DDSA: setup as and b10125
phase is adjustable by 11.25 degrees from 0 to 180 degrees
Set ac and bc for 180 degrees out of phase. as and bs for in phase
100Hz   example
100kHz  example
1MHz    example
pins    list pins
When unit powers on DDSB lags DDSA by 90 degrees

Arduino Sleep

I needed to put an Arduino into a low power standby / sleep mode and then have it automatically wake up. I put together this Sleeper class which I have since found useful, thought I’d share it here.

A couple of notes: It uses the watchdog timer (the only timer left running when the CPU is powered down), so if you’re using a watch dog then this may not be what you’re after. Also, it’s not a very accurate timer (a few ms) so don’t use this for highly accurate timing operations – it’s fine for most applications that need to sleep / power down for a period of time and then wake up do some computing and then go back to sleep.

An example program is below, which is made up up of 3 files:

  1. The main .ino Arduino file.
  2. Sleeper.h
  3. Sleeper.cpp

Main File (example usage)


#include "Sleeper.h"

Sleeper g_sleeper;

// Initialisation code
void setup(void) {
    Serial.begin(9600);
    Serial.println("Setup done.");
}

// Main loop
void loop(void) {
    Serial.print("Total milliseconds awake: ");
    Serial.println(millis());
    delay(100);

    // Power down for 10 seconds.
    g_sleeper.SleepMillis(10000);
}


 

Download Example Project

If you’re just using this lib then you can grab the example project here. You don’t need to read the rest of this code unless you’re particularly interested, for those that are here are the .h and .cpp files:

The class I’ve put together uses the watchdog timer, so if you’re using that for you project then you’ll have to do it another way. The reason for using the watchdog timer instead of one of the normal timers in the Atmega (328p in this case for my testing) is that all normal timers are disabled when the CPU goes into the sleep mode and thus would never wake up if were waiting for one of those to tick! Hence why calling millis() will only show how long the CPU has actually been awake – it’s the time that the normal timers were ticking.

Sleeper.h


#ifndef SLEEPER_H
#define SLEEPER_H

#include <arduino.h>
#include <avr/sleep.h>
#include <avr/power.h>

// This sleeper class utilises the watchdog timer as the oscilators for the timers get powered off in SLEEP_MODE_PWR_DOWN
// mode, which is the most power effcient sleep mode. The sleep time will not be more than 16ms accurate.

class Sleeper {
    public:
    void SleepMillis(long millis);

    private:
    void DoSleep();
    void SetupWatchdog(uint8_t prescalar);

    // Watchdog Prescalars
    const int static NumberOfPrescalars = 10;
    uint8_t Prescalars[NumberOfPrescalars] = {9, 8, 7, 6, 5, 4, 3, 2, 1, 0};
    long Times[NumberOfPrescalars] = {8000, 4000, 2000, 1000, 500, 250, 128, 64, 32, 16};
};

#endif



Sleeper.cpp


#include "Sleeper.h"

   // The interupt vector for the watch dog must be present, even if empty else the CPU resets.
ISR(WDT_vect) {
    // Do nothing.
}

/// Sleeps the arduino for a number of milliseconds
void Sleeper::SleepMillis(long millis) {

    // OPTIONAL delay to wait for all things to finish, e.g. serial prints - else you may get garbled serial prints from sleeping before the sending has finished.
    //delay(50);
 
    uint8_t prescalar = 0;

    // Sleep for the longest possible watchdog timeout that's less than millis and keep going until there are no millis left.
    while (millis > Times[NumberOfPrescalars-1]) {
        for (int i = 0; i < NumberOfPrescalars; ++i) {
            if (millis > Times[i]) {
                prescalar = Prescalars[i];
                millis -= Times[i];
                break;
            }
        }
        SetupWatchdog(prescalar); 
        DoSleep();
    }
}

/// Sets up the watchdog to timeout after a certain time period.
/// There are many comments / notes in this function which have been copied directly from the data sheet for
/// user convenience.
void Sleeper::SetupWatchdog(uint8_t prescalar) {

    // Prescalars can be: 0=16ms, 1=32ms, 2=64ms, 3=125ms, 4=250ms, 5=500ms, 6=1sec, 7=2sec, 8=4sec, 9=8sec
    if (prescalar > 9) {
        prescalar = 9;
    }

    // _BV is a macro that can be thought of as a funtion that takes in a number and outputs a byte with that bit set.
 
    // WDTCSR - Watchdog Timer Control Register
    // Note that WDP[0-3] is not in order (WDP[0-2] is but WDP3 is actually bit 5 not 3!) so we have to preprocess the prescaler passed in above.
    // bits 7 = WDIF, 6 = WDIE, 5 = WDP3, 4 = WDCE, 3 = WDE, 2 = WDP2, 1 = WDP1, 0 = WDP0
 
    // WDP3 WDP2 WDP1 WDP0 Typical Time-out at VCC = 5.0V
    // 0 0 0 0 16ms
    // 0 0 0 1 32ms
    // 0 0 1 0 64ms
    // 0 0 1 1 0.125 s
    // 0 1 0 0 0.25 s
    // 0 1 0 1 0.5 s
    // 0 1 1 0 1.0 s
    // 0 1 1 1 2.0 s
    // 1 0 0 0 4.0 s
    // 1 0 0 1 8.0 s
 
    // Take the first 3 bits (WDP[0-2])
    uint8_t wdtPrescalarBits = prescalar & 7;

    // Now we need to set WDP3, to do this we don't set bit 3 but bit 5, so if our presclar had bit 8 set i.e. it 
    // was 8 or 9 being passed in then we must set WDP3 accordingly, else we could have just used prescar as it was passed in. 
    if ( prescalar & 8 ) {
        wdtPrescalarBits |= _BV(WDP3);
    }
 
    // MCUSR – MCU Status Register
    // The MCU Status Register provides information on which reset source caused an MCU reset.
    // MCUSR Bit 3 – WDRF: Watchdog System Reset Flag
    // This bit is set if a Watchdog System Reset occurs. The bit is reset by a Power-on Reset, or by writing a logic zero to the flag.
    MCUSR &= ~_BV(WDRF);
 
    // WDTCSR Bit 4 – WDCE: Watchdog Change Enable
    // This bit is used in timed sequences for changing WDE and prescaler bits. To clear the WDE bit, and/or change the prescaler bits, WDCE must be set.
    // Once written to one, hardware will clear WDCE after four clock cycles.
 
    // WDTCSR Bit 3 – WDE: Watchdog System Reset Enable
    // WDE is overridden by WDRF in MCUSR. This means that WDE is always set when WDRF is set. To clear
    // WDE, WDRF must be cleared first. This feature ensures multiple resets during conditions causing failure, and a
    // safe start-up after the failure

    // Allow changes
    WDTCSR = _BV(WDCE) | _BV(WDE);

    // WDTCSR Bit 6 – WDIE: Watchdog Interrupt Enable
    // When this bit is written to one and the I-bit in the Status Register is set, the Watchdog Interrupt is enabled. If WDE is cleared in combination with this setting, the Watchdog Timer is in Interrupt Mode, and the corresponding interrupt is executed if time-out in the Watchdog Timer occurs. If WDE is set, the Watchdog Timer is in Interrupt and System Reset Mode. The first time-out in the Watchdog Timer will set WDIF. Executing the corresponding interrupt vector will clear WDIE and WDIF automatically by hardware (the Watchdog goes to System Reset Mode). This is useful for keeping the Watchdog Timer security while using the interrupt. To stay in Interrupt and System Reset Mode, WDIE must be set after each interrupt. This should however not be done within the interrupt service routine itself, as this might compromise the safety-function of the Watchdog System Reset mode. If the interrupt is not executed before the next time-out, a System Reset will be applied.
    // Note: 1. WDTON Fuse set to "0" means programmed and "1" means unprogrammed.

    // Watchdog Timer Configuration
    //WDTON WDE WDIE Mode Action on Time-out
    //1 0 0 Stopped None
    //1 0 1 Interrupt Mode Interrupt
    //1 1 0 System Reset Mode Reset
    //1 1 1 Interrupt and System Reset Mode Interrupt, then go to System Reset Mode
    //0 x x System Reset Mode Reset
 
    // Perform the change.
    WDTCSR = _BV(WDCE) | wdtPrescalarBits | _BV(WDIE);
}

/// Powers down system.
void Sleeper::DoSleep() {

    // Set the sleep mode.
    set_sleep_mode(SLEEP_MODE_PWR_DOWN); 
    sleep_enable();

    // Put the device into sleep mode.
    sleep_mode();

    // System continues execution here after watchdog timeout.
    sleep_disable();
}

ESP8266 NodeMCU 0.9 Arduino C++

A friend from work let me borrow an ESP8266 module on a NodeMCU board and I went about trying to get started with it and thought I’d write up my notes. (using C++)

nodemcu

To make this easier I’m going to use the easily available Arduino IDE and a set of libraries for the ESP8266 module.

Install Arduino IDE

Arduino download page.

Add libraries

Using these libraries you can follow the steps below to enable your Arduino IDE to work with many ESP8266 modules. https://github.com/esp8266/Arduino

Using the Arduino IDE Boards Manager:

  • Start Arduino and open Preferences window.
  • Enter http://arduino.esp8266.com/stable/package_esp8266com_index.json into Additional Board Manager URLs field. You can add multiple URLs, separating them with commas.
  • Open Boards Manager from Tools > Board menu and install esp8266 platform.
  • Select your ESP8266 board from Tools > Board menu.

 

Code for the ESP8266 server

#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>
#include <ESP8266mDNS.h>

const char* ssid = "YourWifiName";
const char* password = "YourWifiPassword";

void HandleRoot();
void HandleCommand();
void FlashLeds();
void TurnOffLeds();
void HandleNotFound();


ESP8266WebServer server(80);

// what to do when a client requests "http://<IP>"
void HandleRoot() {
    server.send(200, "text/plain", "hello from esp8266!"); 
}

// What to do when a client requests "http://<IP>/command"
// This function doesn't care whether it is a GET or POST at the moment, 
// it will treat them the same just to make things easier.
void HandleCommand() {
    for (uint8_t i=0; i<server.args(); i++) {
        if (server.argName(i) == "flash") {
            if (server.arg(i) == "on") {
                FlashLeds();
                server.send(200, "text/plain", "LEDs turned on");
            } else {
                TurnOffLeds();
                server.send(200, "text/plain", "LEDs turned off");
            }
        }
    }
    server.send(404, "text/plain", "Command not found");
}

void FlashLeds() {
    // Flash LEDs or something then exit this func so that the webserver carries on running.
    Serial.println("LEDs on.");
}

void TurnOffLeds() {
    // Flash LEDs or something then exit this func so that the webserver carries on running.
    Serial.println("LEDs off.");
}

// If the route is not found then default to printing out what was sent to this server to aid in debug.
void HandleNotFound() {
    String message = "File Not Found\n\n";
    message += "URI: ";
    message += server.uri();
    message += "\nMethod: ";
    message += (server.method() == HTTP_GET)?"GET":"POST";
    message += "\nArguments: ";
    message += server.args();
    message += "\n";
    for (uint8_t i=0; i<server.args(); i++) {
        message += " " + server.argName(i) + ": " + server.arg(i) + "\n";
    }
    server.send(404, "text/plain", message);}

void setup(void) {
    Serial.begin(115200);
    WiFi.begin(ssid, password);
    Serial.println("Setting up.");

    // Wait for connection
    while (WiFi.status() != WL_CONNECTED) {
        delay(500);
        Serial.print(".");
    }
    Serial.println(".");
    Serial.print("Connected to ");
    Serial.println(ssid);
    Serial.print("IP address: ");
    Serial.println(WiFi.localIP());

    if (MDNS.begin("esp8266")) {
        Serial.println("MDNS responder started");
    }

    /* 
    //I left this in commented out just to show this method as well.
    server.on("/inline", []() {
        server.send(200, "text/plain", "this works as well");
    });
    */
 
    // Setup server routes.
    server.on("/", HandleRoot);
    server.on("/command", HandleCommand);
    server.onNotFound(HandleNotFound);
    server.begin();
    Serial.println("HTTP server started");
}

void loop(void) {
    server.handleClient();
}

 

Code for webpage to send commands

The below code can be copied and pasted into a text file and then save the file as yourfilename.html, it will allow you to send commands to the module / server.

The IP address of my module is 192.168.1.115 so make sure you replace that IP in the below code with the one of your module.

Also note that you could actually serve this page straight from the module itself and not have  separate file.

<head>
    <!--<script src="jquery.js"></script>-->
    <script src="http://code.jquery.com/jquery-2.2.1.min.js "></script>

 <script>
    $(function () {

    // Bind the click of the button to the function that posts data to the server.
    $('#buttonFlashOn').click( function() {
        $.post("http://192.168.1.115/command", { flash : "on"})
         .done(function( data ) {
             //alert( "Data returned: " + data );
        });
    });

    // bind the click of the button to the function that posts data to the server.
    $('#buttonFlashOff').click( function() {
        $.post("http://192.168.1.115/command", { flash : "off"})
         .done(function( data ) {
            //alert( "Data returned: " + data );
          });
    });

});
</script>
 
 
</head>

<body>
 <br/>
 <button id="buttonFlashOn">Flash on</button> 
 <br/>
 <br/>
 <button id="buttonFlashOff">Flash off</button>
 <br/>
</body>

enjoy 🙂

Dual AD9850 in Quadrature ( Dual DDS with Adjustable Phase )

Within this article the author John ( G8GKU ) indicates how two typical AD9850 modules may be utilized and controlled so as to produce the often needed pair of R.F. carriers with a quadrature phase relationship, sine or cos (0° or 90°). These modules are readily available from online suppliers.

A pair of quadrature related sine waves are useful if not mandatory when making SSB modulators / demodulators, image reject mixers, Weaver method SSB ( Tx / Rx ) and similar items.

An accompanying demonstration code has been written to allow a user to set output frequency and phase of the two DDS units.

The popular Arduino™ platform has been chosen as controller for the DDS synthesisers, C++ Arduino code being written by Alex. However, to broaden the appeal, a version has also been coded which will run on a PIC™ 16F876 28 pin DIP device, written by G8GKU, providing home-brew readers an opportunity to warm up the soldering iron and build a system from scratch.

Download of the necessary hex file for the PIC 16F876 is available :
Hex_Files

… and the Arduino equivalent is available here:
dds

Source of information regarding the Analogue Devices™ AD9850 devices is to be found at http://www.analog.com/media/en/technical-documentation/data-sheets/AD9850.pdf

A very specific and relevant application note by Analogue Devices is to be found at http://www.analog.com/media/en/technical-documentation/application-notes/AN-587.pdf

The author wishes to acknowledge, with thanks, the detailed technical information contained within the two publications by Analog Devices.

Photograph 1 shows the pair of DDS modules used by the author, however, there are other schematically similar but physically different PCBs to be obtained, the on-board circuitry is very similar and readers may well use their own choice of DDS module.

Photograph 1:

CROPPED-Pre-assembly

Overall the process consists of two parts:

Part one details the hardware implementation.

Part two introduces the procedure required to drive the physical DDS cores to produce the quadrature output waveforms.

Hardware Implementation

One small change is required to just one of the two DDS PCBs. The two DDS units are then physically mounted such that the DDS modules are close together, in a position which will allow the high speed crystal clock signal from one unit to be jumper-wired across to the second unit.

To allow the generation of frequency and phase locked quadrature outputs it is essential that both DDS modules share just one clock oscillator.

Note, Photograph 1 shows the M3 mounting screw-head adjacent to the double row of PCB connector pins, with the inside edge of the screw head filed back 1 mm. This is suggested as the DDS PCB has a signal track rather close to the hole. It is under the white indent paint and can easily be overlooked. Do this, at one position, for both DDS PCBs.

Photograph 1 indicates how the two DDS modules should be mounted, side by side and long-ways reversed, with ca 5.5 mm lengthways offset between them. This mounting method will allow the 125 MHz clock from one module to be passed across to the second module via a short wire, so keeping the two DDS clock signals closely in step as per the AN-587.pdf application note. Also visible is the strongly suggested addition of a short length of single sided PCB material, resulting in an overall mechanically firm assembly.

Required physical modification of DDS PCB.

It is required to modify just one of the DDS PCB modules, plus an extra option as mentioned below. In overview, one resistor is to lifted up on end and then a wire link added using as short a length of wire as is possible. Please view the wiring schematic sheet at this stage.

Method

Re photograph 1, the resistor to be lifted is indicated by the adjacent ink mark line on the joining PCB. This is a 100 Ohm resistor fitted between the xtal clock oscillator signal output ( pin 3 ) and the clock input to the AD9850 chip itself. The resistor and local circuitry is shown in very simplified fashion within the wiring diagram.

Choose one module on which the resistor is to be lifted to the vertical, on this DDS PCB the clock oscillator is then unused.

Using a small soldering iron, heat the soldered resistor pads by swapping end to end and lift it off the PCB with small tweezers, putting it in a safe place! Note, apply enough heat to not only melt the solder but also perhaps to melt the mounting glue used to affix the resistor before it was originally soldered.

Once the resistor is removed, ensure the then exposed PCB pads are free of solder spikes, perhaps applying a little new solder to the one vacated solder pad which is the inside-most pad, i.e. far-most from the PCB edge.

Refit the resistor so as to be vertical, on the inside pad, soldering one end to the pad. This so-called ‘ Tombstone ‘ fitting of the resistor is not strong and appropriate care is now needed when handling the PCB.

Consider the optional modification as below and then proceed to assemble the two PCBs side by as in photograph 2.

Photograph 2:

CROPPED-assembled

An Optional Modification

Optionally, the now unused clock oscillator module upon the DDS PCB which has been modified may be disabled, i.e. turned off. This has the advantage of saving the 40 mA or so of oscillator power supply demand and also removing the presence of an un-needed 125 MHz carrier which may be detected by nearby suitable Rx’s. To disable the oscillator module, proceed as follows :

Locate the oscillator module, it is the 7 x 5 mm metal clad component near the board edge.

Next locate pin 1 of the crystal module, it is the pin which is closest to both the board edge and the nearest PCB mounting hole. It is adjacent to the ‘ dot ‘ on the oscillator top cover. Clean off the paint from a 2 mm section of the PCB ground track just adjacent to pin 1 of the oscillator, there is a thin continuous ground track which runs past the module. Solder a 1.5 mm link, using thin wire, between the pin 1 pad of the oscillator and the cleaned PCB ground track. Job done.

Assembling the Two DDS Units

This section addresses assembling the two DDS units to be one module.

To provide workable physical strength a short strip of single sided copper clad PCB material is used to join two DDS PCBs, this may be glass fibre ( fiber ) PCB or other material. Suggested size of the material is 46 mm long by 11 mm wide. Two holes along each long edge are required, which are easiest to mark with a felt tip pen through the fixing holes of the DDS modules.

Mark holes on to the glass side and not the copper side of the strip. Photograph 1 shows the general technique.

Repeated from above ;

Please note, viewing Photograph 1 shows the M3 screw-head adjacent to the double row of PCB connector pins to have the inside edge of the screw head filled back 1 mm, this is needed as the DDS PCB has a signal track rather too close to the hole. It is under the indent paint and can easily be overlooked. Do this for both DDS PCBs.

The assembly is completed by bonding the pair of DDS PCBs together with the PCB joining material by use of either M3 or other slim nuts and bolts. Ensure that the joining plate has the copper side *away* from the DDS PCBs when assembling, take care not to allow a screwdriver to slip during this process, as it may damage surface mount components of the DDS modules.

These 4 non-insulated screws provide, via the PCB material, an additional overall ground connection between the two DDS modules. To allow the combined dual DDS assembly to stand on the bench for testing the author has fitted longer M3 screws to the four outside fixing holes to act as legs, nothing more.

Before fitting the clock signal wire link, carefully clean off the ground-track paint from a 3 mm length of the track, at both PCB edges just between the two 100 Ohm resistors. Once clean add a small ( about 2 mm ) bridging solder blob so as to join the ground tracks. This can be seen on photograph 2 ( it may be easier to remove the paint before final physical assembly ).

The wire link for the clock signal is next to be fitted. It should be taken from the non-modified DDS PCB, from the *outside* pad of the corresponding resistor which was lifted on the other PCB. The destination of the wire link is the top, i.e. free end, of the uplifted 100 Ohm resistor.

Use of thin enameled wire will reduce risk of unwanted connections, the wire link is about 7 mm long.

Physical Wiring

dds_schematic_bitmap

The diagram above shows the detailed pin to pin data signal wiring between the controller and the DDS module. However, please read the following application note.
Application note AN587 places strong emphasis upon the need for both the master clock to the DDS modules and the Frequency Update ( Fr_updt ) pulse to be applied with the same time, phase, relationship to each DDS in the system.

In particular the Fr_updt pulse should be a fast rising edge and should have the same physical wire length between the CPU / controller pin and to each DDS unit. It is *not correct* to daisy chain the Fr_updt pulse from the controller to a first DDS and then to pass it along to the second unit. The correct method is to fork, i.e. to split, the wire a little way before the wire reaches either one of the DDS units and to take one each of the forked wires to a DDS Fr_updt pin, keeping the wire lengths after the fork to closely the same length. This method will provide a Fr_updt pulse well time-matched to each DDS core. Likewise, the link-jumpered master clock signal will also be as close to in-phase as can be practically done by the close physical assembly of the two DDS units.

Part two introduces the code and operating procedure required to drive the physical units.

There is little in the code which will not be found in just about any code which drives an AD9850 module. What makes the code for this article different is that it sequences the start up of the two DDS cores in the correct fashion so as to run not only at the same frequency but with a quadrature phase relationship.

The DDS start-up sequence in this article refers to the DDS units as operated in the serial mode of data loading, the parallel mode is only slightly different.

Overall the process consists of clearing all of the registers of the two DDS units and then starting both, at the required frequency and phase angle with a single “ go “ pulse from the controller. However, the AD9850 has some subtle characteristics which are important to the start up process.

Although contained within the data sheet, what is quite easily overlooked is the information that the AD9850 device always starts-up in parallel data entry mode at any reset pulse application.

Added to this situation is that the very first ‘ data-load clock pulse, Fr_updt pulse pair ‘ ( in that order ) following a reset always has the task of loading the internal 8 bit control and phase register of the AD9850, keep in mind this is an 8 bit parallel load event. In this application, it is required that the AD9850 starts at “ zero phase “ or some other selectable phase. Therefore the relevant bits of the phase control word must be pre-set, at the very first ‘data-load clock pulse Fr_updt pulse pair’. The solution used in this article is to ensure the phase control bits are held, wired, at logic 0 during the first data-load clock pulse, Fr_updt pulse pair. It is then the responsibility of the second update pulse to load the actual required phase and frequency data. To ensure the initial phase select bits are at logic zero, in the case of the DDS module used by the author, the data pins, D3, 4, 5, 6 are wire linked to zero volts, i.e. the ground pin of the connector. Data bits 0, 1 are set to logic 1 by on-PCB pull up resistors and a two pin link, J1, grounds data bit 2, giving a byte of 0b00000011, the link is to be fitted. Data bit D7 is the serial data input pin and should be set to logic zero by the CPU and code during the initial stages of the start-up process.

Note, in some of the internet supplied circuit diagrams for the DDS modules used, the schematic has the D7 and GND schematic pins shown in reversed order to the printed indents on the physical PCB. In the case of the PCBs used by the author the indents of the PCB are correct, where GND is the end pin, see photograph 1.

Referring to the AD9850 data sheet, page 24, it will be seen that a pulse applied to the Reset input pin to the AD9850 has the effect of resetting the device to a known zero phase state and with the internal registers also at a known state.

* The reset pulse does not clear or reset the data input shift register. *

Once the reset pulse has passed, the DDS core commences operation and hence the phase accumulator registers will advance in phase at each cycle of the DDS clock. The actual advance in phase and frequency entirely depends upon the initially random bits as will be in the frequency control registers at power up time. So to control both the phase and frequency of the DDS cores once running it is required that the cores are reset, loaded with all zero’s and then reset again, before receiving the user required phase and frequency data. With the above subtlety in mind it can now be understood that a specific start-up sequence is needed to start the two DDS cores in phase and frequency lock step.

To recap, to achieve quadrature operation, it is to essential to reset the device twice and to ensure the correct, i.e. required, phase start-up bits have been set in the control word.

The required detailed sequence is ;

1 Ensure DDS parallel data pins are set as 0,1 high, 2, 3,4,5,6, low.

Above is either hard wired on the PCB or set via appropriate connectors.

Set D7 to logic low, zero, via the CPU and code.

2 Apply first Reset pulse.

Loads phase and control words such that the module will be in “serial“ mode, and phase position = 0°.

3 Serial mode, to each DDS core, shift in 32 all zero bits of frequency bits and 8 bits for phase bits i.e. 0b00000011. Same data to both.

4 Apply one Fr_updt pulse, wired in parallel to both DDS units. Frequency and phase registers now contain “zero frequency and zero Phase”.

5 Apply a second Reset pulse. DDS core does not increment away from all-zero’s as the increment and phase registers are set to all zero’s.

6 Serial mode, shift in 32 bits of required frequency and 0b01000011 for phase bits. To one DDS core only. ( this is the 90 degree offset DDS )

7 Serial mode, shift in 32 bits of required frequency and 0b00000011 for phase bits. To the second DDS core only. ( this is the 0 degree offset DDS )

7 Apply one Fr_updt pulse. The DDS cores now generate the required frequency and will have the as set phase relationship.

In Operation

Once the dual DDS assembly has been completed and connected to the controller of your choice, by whatever is your usual means, load the hex file and run the controller.

It has been assumed that the user will have some form of ASCII serial terminal available on a PC, e.g. HyperTerminal™, TerraTerm or a similar application. Embodied within the code is a simple help menu, which will respond with an on-screen command list. The help menu is invoked by sending the ‘?’ character followed by a new line via the terminal.

The user is given control of each DDS core, each may be set to provide 0 or 90 degrees phase. This allows either DDS core to be either behind or ahead in phase by the required 90 degrees.

Also each DDS module may be set to the same phase, i.e. both at 0 or both at 90 degrees.

Upon initial power-on the controller will set the two cores to be at 1 MHz and at 90 degrees relative to each other.

A testing hint, if you set up to view the two output signals with a dual channel oscilloscope, ensure that the two oscilloscope probes have the same delay / bandwidth factors, if not then expect to see a small and frequency dependent phase shift between the signals.

In most cases where 0, 90 degree signals are used in quadrature mixer drive, it is usual to be able to carefully adjust the amplitude of the signals to be identical. This is relatively easy using the two DDS cores. If one of the two DDS modules is producing a slightly different o/p amplitude this can be corrected. Keeping in mind the note above regarding use of matched oscilloscope probes, determine which of the two DDS core is producing the largest amplitude signal and for that module make the following simple modification. In the case of the DDS modules used by the author, there is a two pin link, labelled J3, this is the ground end of the 3k9 resistor which is used to set the o/p amplitude of the DDS signal, the 3k9 connects to pin 12 of the AD9850.

Obtain a physically small 500 Ohm or similar variable resistor. Solder the wiper and one track-end pin across the DDS module PCB-side of the J3 connector and remove the link. Restart the modules and using the 500 Ohm adjust the o/p signal amplitudes to be the same, as shown in photograph 3, which is two 1MHz carriers adjusted to be equal amplitude ( and in phase ).

Photograph 3:

CROPPED-in-phase

 

Two signals in quadrature

Photograph 4 shows two 1MHz carriers in quadrature.

Photograph 4:

CROPPED-quadrature

 

Amplification

As the output signal of the non-amplified DDS core is not sufficient to drive a level 7 mixer, some form of amplification is often used. In the case of quadrature signals it is important that amplification of both signals is identical in amplitude and phase delay.

Notes for 16F876 28 pin version.

The required hardware circuit is essentially the minimum needed to run the device.
In addition to the usual Vcc and ground supplies, plus decoupling capacitors, add as below :
A crystal of 4 or 10MHz at the Osc1 and 2 pins, with 22pF each to ground.
A 100k Ohm pull up resistor on the !MCLR pin to Vcc.
Fit the standard MAX232 style serial interface device to port pins C6 and C7
Load hex code using your usual method.
In both the 4 and 10MHz hex versions the serial port uses 9600,8,none,1,none
i.e. 8 data bits, no parity, 1 stop bit, no flow control.

Connect the DDS units as …
W_CLK_1 PIN_C0          // CHIP PIN 11
W_CLK_2 PIN_C1          // CHIP PIN 12
Serial_Data PIN_C2      // CHIP PIN 13
RESET_DDS PIN_C3      // CHIP PIN 14
Freq_Update PIN_C4    // CHIP PIN 15

Enjoy!

G8GKU 73

Arduino IsNumeric Function

I needed an “IsNumeric(string)” function for some Arduino code I’m writing and couldn’t find one in the libraries so I thought I’d share mine:

EDIT: updated version:

boolean isNumeric(String str) {
    unsigned int stringLength = str.length();
 
    if (stringLength == 0) {
        return false;
    }
 
    boolean seenDecimal = false;
 
    for(unsigned int i = 0; i < stringLength; ++i) {
        if (isDigit(str.charAt(i))) {
            continue;
        }
 
        if (str.charAt(i) == '.') {
            if (seenDecimal) {
                return false;
            }
            seenDecimal = true;
            continue;
        }
        return false;
    }
    return true;
}

 

boolean isNumeric(String str) {
    for(char i = 0; i < str.length(); i++) {
        if ( !(isDigit(str.charAt(i)) || str.charAt(i) == '.' )) {
            return false;
        }
    }
    return true;
}

There are a few tweaks that should be added (such as counting decimal points as this would currently except 12.34.56 which is clearly not a valid number), this is a good starting point however.

Hope that’s useful for someone!

relocation truncated Arduino Bug

I’ve been using the Arduino Mega 2560 recently for my robotics project and after getting a fair way through some code I found that I started to receive this error:

relocation truncated to fit: R_AVR_13_PCREL

It turns out there is a bug in the arduino IDE or at least the compiler it uses. The solution was to Download the Beta Arduino IDE which has the bug fixed in it.

That fixed it for me. Happy tinkering.

 

For anyone that is interested: I think that the bug was due to a compiler flag or config error which disabled the option for the compiler to use long jumps. So as your code grows you gradually fill more memory and the device cannot use its short (more efficient) jump instructions to move around memory as you have now used more than 4kB which I believe is the limit of the short jump instruction on those chips.