Panoramic Camera Head

The Pano Head is a rotating platform for a camera that mounts on a tripod and is controlled from your Windows Phone 8 device over Bluetooth. You can use it to take a series of pictures that you can stitch up with Photosynth.

I’ve always wanted to take panoramic pictures with my camera from a tripod, so I decided to make a remote controlled panoramic tripod head that would screw on to any tripod. Using a motor and a shutter control, a series of pictures can be taken. With the use of Photosynth, those pictures can be stitched up to make an interactive 360-degree view. You can also use your Windows Phone 8 as a remote shutter release over a Bluetooth connection. Windows Phone 8 has a Bluetooth API that allows us to program connectivity using TCP/IP style sockets.

This is a popular project in the Arduino world and I wanted to complete it using Gadgeteer hardware and the .NET Micro Framework (NETMF). This project uses the FEZ Cereberus mainboard module, but you should be able to use other Gadgeteer mainboards without any problems.

What is the .NET Gadgeteer platform?

The .NET Gadgeteer platform is an open source toolkit for building small electronic projects using Gadgeteer hardware modules and programmed using the .NET Micro Framework with Visual Studio 2010 or Visual C# Express (2010 edition). The Gadgeteer modules connect together using special cables and very little soldering is required. You typically use a mainboard module, which has the CPU and memory, and then connect various modules (camera, sensors, networking, display, controllers, etc) to accomplish your chosen task.

This part is going to require Visual Studio 2010 C#. Whether the full version or the Express version, it has to be 2010. This is because the Gadgeteer libraries have as of this writing not yet been ported to Visual Studio 2012.

The programming module is easy to work with. Designers are installed into Visual Studio so that you get a visual display of the modules and how they are connected to each other.

image

You also get full Intellisense for the modules as you code and the best part is that you get to interactively debug through the Visual Studio IDE. In the reference assemblies, you can see a set of assemblies that start with “Microsoft.Spot”. The NETMF came out of the SPOT initiative. SPOT was the underlying technology used in a line of “smartwatches” and selected GPS receivers. The “Spot” assemblies make up the core of the NETMF and the Gadgeteer assemblies provide the “glue” between NETMF and Visual Studio. Also, the GHIElectronics assemblies provide the support for the GHI Gadgeteer modules.

Determining your camera’s remote shutter circuits

You will need a camera that has the ability to use a wired shutter release cable. Most DSLRs and a few point ‘n shoot cameras have a connector for a remote. The camera that I used for this project, a Panasonic Lumix FZ-30, falls in that category. You’ll also need to determine how your camera’s remote is wired. There is a great resource at http://www.doc-diy.net/photo/remote_pinout/ that lists the circuits and connectors for many popular camera makers.

Most of them use two wires, one to control the focus, the other to trip the shutter. My Lumix uses a single wire and controls the focus and shutter by changing the resistance over the wire. The hardest part here is getting the connector. I bought a generic Lumix wired remote online for a few dollars and then disassembled it to use the wiring and plug for this project.

Hardware list

PartDescriptionCost

FEZ Ceberus Basic Kit

This is a Gadgeteer starter kit that comes with the mainboard, plus some modules. You get a FEZ Cerebus mainboard with a 168Mhz 32bit Cortex M4 processor with floating point, 1MB FLASH and 192KB RAM. Also included are a USB module to supply power and provide a USB connection to the PC, a LED module for blinkenlichten support, and a joystick module.

$50

Extender Module

Used as a breakout board for the camera and servo pins.

$5

10 pin Header block

Soldered to the extender module to make it a breakout board.

$1

4 pin header block

Soldered to camera control board to control focus and shutter.

$1

Crimp pins

Used to make custom jumper wires for easy assembly.

$8

Bluetooth Module

Provides access to Bluetooth using the SPP serial profile.

$40

USB Portable Charger

Provides power to the mainboard and servo motor. Needs to have a mini USB connector.

$20

Generic Camera Shutter Release cable

For around $7, I was able to get a generic shutter release cable for my camera. It provides the cable needed and a verification of the necessary wiring.

$10

2N222 NPN transistors

Used to allow the device to open and close the focus and shutter connections. Two are needed.

$4

Resistors

The combination of resistors that tell the camera what action was requested. Three are needed for the Panasonic Lumix camera that I used.

$15

Mini circuit board

Small circuit board to hold resistors, transistors, and header block.

$8

Wood

Two pieces of ¼” thick board. The bottom piece is 6” square. The top is 10” by 6”.

$5

Lazy Susan Turntable

A ball bearing turntable that allows the top part of the base to rotate around the bottom. This allows the top to rotate around the bottom freely. Available from most hardware stores. Get the one that is 6” square.

$10

Futaba Servo

Parallax (Futaba) Continuous Rotation Servo to rotate the camera a full 360 degrees in either direction

$16

Brad Hole Tee Nut

Size: 1/4-20 x 5/16. Creates the tripod socket to allow the Pano Head to be mounted to a standard tripod. Place a section of the yardstick over the tee nut to make it flush mounted.

$3

Reduction gears

Convert the high speed/low torque rotation from the servo, to a higher torque/lower speed on the camera head. I used part # RHA32-26-60.

$8

Servo gear

Replaces the standard horn on the servo with a gear that will drive the reduction gear. I used a RSA32-2FS-20 from Servo City.

$5

Electrical boxes

Cheap and easy way to mount the components.

$5

yardstick

Chopped up as used for the assembly.

$1

Thumbscrew

Used to mount the camera to the pano head. The size is ¼”-20 x 1/5”.

$1

Wires

22 – 26 gauge.

$8

Screws, nuts, and bolts

Various screws, bolts, washers to mount everything.

$10

Liquid electrical tape

Used on the soldered wire connections.

$6

 

Total

$240

Getting started with the FEZ Cerebus

GHI has a detailed tutorial for getting up and running on the FEZ Cerebus Mainboard. If you are new to the Gadgeteer world, start with the FEZ Cerebus Basic Kit Guide on the Cerebus download page. After installing the compiler and SDK bits, you’ll want to make sure that the board has the correct firmware on it. Detailed instructions on loading the firmware are on the TinyCLR wiki. I recommend using the tutorial to make sure that everything works before moving on.

Once we have the compiler and SDK bits installed along with the tutorial compiles and runs, it’s time to start building this thing.

Building the camera control board

The first part that I assembled was the mini-circuit board for the camera shutter control. I used a single wire to control the focus and the shutter—changing the resistance triggers the camera. So we need to short the circuit between the resistors to get the camera to focus and take a picture. We also want to isolate the circuit from the power coming from the Gadgeteer board. The camera supplies its own power to the circuit and the last thing we want to do is to fry our camera. Accordingly, we’ll use a pair of 2N222 NPN bipolar junction transistors—they are cheap; I bought a bag of 15 from RadioShack for about $3.50—and we’ll use general-purpose input/outputs (GPIO) pins from the Gadgeteer board to toggle the transistors.

This is the circuit that I used for my camera:

image

The jack is part of the cable that I took from the generic wired remote. Having a wired remote that you can take apart will make this project much easier. You can buy just the jack, but there really isn’t much of a difference in price and it’s easy to match up the wires and resistors after you disassemble the remote.

When the transistors are turned off, the camera shutter circuit flows through all three resistors and the combined resistance is below the threshold that triggers the camera. When we turn on the focus transistor, the combined resistance drops from 41.1kΩ to 5.1 kΩ. That gets the camera’s attention and puts it in focus mode. Then, turning on the shutter transistor, the resistance drops to 2.2kΩ and the camera takes the picture. You can test this circuit out with a breadboard and use jumpers instead of the transistors. If you can’t find exact matches for the resistors, try to get as close as possible. I was able to use 2k instead of 2.2k and the circuit still worked.

For Canon and Nikon DSLR cameras, the circuitry will be much simpler. Instead of using three resistors on one circuit, you will have separate circuits for focus and shutter, though the two will share a common ground. A Canon circuit might look like this:

image

I soldered the components to a small circuit board and used short jumper wires to connect the 3 lines from the header block to the Gadgeteer extension block. If I ever decide to use this with a different brand of camera, I can make a new board to match that circuit and connect it to Gadgeteer.

The red and black wires coming off to the left of the circuit board are connected to the positive and negative wires from the remote cable. The two red wires on the right side of the circuit board are connected to the GPIO pins (pins 3 & 4) on the extender module, and the black wire goes to ground (pin 10) on the extender. Also shown are the lines for the servo motor. Red going to the 5v line on pin 2 of the extender, green to the PWM line (pin 7), and ground is shared with the circuit board on the ground pin.

Building the extension block

Use a GHI extender module to provide access to power, pulse, GPIO, and ground pins from the mainboard. As shipped by GHI, it looks like this:

image

For ease of assembly and testing, I soldered a 10 pin header block to the board. In the Gadgeteer library, the pins are numbered from 1 to 10. For the camera control, we’ll be using pins 3, 4, and 10. On the mainboard, I connected this module to socket 5. Each socket on a mainboard has a unique number to identify it, and one or more letters to list the socket capabilities. Each socket can have different electrical and communications capabilities for its pins. Some types support Ethernet signals, others provide the control lines for an SD card controller.

When you pick out a Gagdeteer module, it will list the socket type requirements. For example, the Bluetooth module requires a socket with type “U” capabilities. Type U provides UART serial port support with pins 4 and 5 used for sending and receiving data. On the FEZ Cerebus mainboard, sockets 2 and 6 provide type U support.

Socket 5 on a FEZ Cerebus has the following types: P, C, and S. The socket type definitions can be found on Gadgeteer CodePlex wiki. The P type includes pulse-width modulated (PWM) outputs on pins 7, 8, and 9, and we need a PWM output to control the server motor. The S type has pins 3, 4, 5 set as general-purpose input/outputs (GPIO) and we need 2 of them to control the camera. The camera control and the servo share pin 10, which is the ground line. Pin 2 provides 5v of power and will be used to power the servo motor.

Connect each module to the mainboard to the following sockets:

Power

Socket 8

Joystick

Socket 3

Extender

Socket 5

Bluetooth

Socket 6

LED

Socket 7

 

Usually when you work with a Gadgeteer module, you drag the mainboard and modules from the toolbox onto the designer. Then you use the mouse to connect the mainboard to the module by dragging from one socket to another. The designer does some code-behind magic and brings in the appropriate reference libraries for each module and then you’re able to start working with the modules. We will do this with the Joystick, Extender, and LED modules. For the Bluetooth module, we will use an open source driver written by Eduardo Velloso. I downloaded the file Bluetooth.cs from Eduardo’s Codeplex site and added it to the project.

Connect the focus line from the camera control to pin 3 of the extender module. The shutter line goes to pin 4 of the extender and ground to pin 10. I made up a set of wires with jumper pins on each end to make it easy to connect the modules and soldered the crimp pins to sections of wire to make my jumper cables. Also, I used liquid electrical tape over the solder connections, which made the connections more secure and reduced the chances that wires in adjacent sockets would touch each other. Completed, I would wire up the camera board and servo motor to the extender module with the following arrangement:

Device wiresGadgeteer Extender Module

Camera Focus

Pin 3 (GPIO)

Camera Shutter

Pin 4 (GPIO)

Camera Ground

Pin 10 (shared with Servo Ground)

Servo power

Pin 2 (5v)

Servo PWM

Pin 7 (PWM)

Servo Ground

Pin 10 (shared with Camera Ground)

Servo motor

For the servo motor, we are using a Parallax (Futaba) Continuous Rotation Servo. This is a servo motor that has been modified to allow a full and continuous 360 degree rotation. We need to connect three lines to the servo: power, ground, and pulse-width modulation (PWM). The PWM signal will determine the direction and rate the Servo turns. When the servo receives a 1.5 ms high pulse, the server will be at center position. Speed and direction are controlled by the length of the pulse, which can range from 1.3 ms to 1.7 ms. For correct operation, the servo needs a 20 ms pause between pulses.

The Gadgeteer SDK provides the PWMOutput class that allows us to define the PWM parameters. The SetPulse() method takes two parameters, period and hightime, which correspond to the pause and high pulse. These values are specified in nanoseconds (ns).

To connect the Servo wiring, connect the power line to pin 2 of the extender module and the ground line to pin 10. I ended up splicing a ground cable as a “Y” cable so that the ground from the extender module could be shared between the camera board and the servo. The white wire coming from the servo is the control line. That gets connected to extender pin 7:

The white wire from the servo goes to the green wire jumper wire and connects to pin 7, which is mapped as PWM pin. The red wire goes to pin 2, which provides 5v. The black wire goes to pin 10, which is ground. The camera circuit is not connected here, which makes it easier to see how the servo was wired up.

Gadgeteer App

With the Gadgeteer hardware assembled, it’s time to get the code working.

You’ll want to get the source code from the CodePlex site–everything is built and we can review the highlights here.

In ShutterControl.cs, we have the code that will tell the camera to take a picture. We have a class called ShutterControl, which descends from Gadgeteer.Modules.Module. The constructor looks like this:

public ShutterControl(int socketNumber)
{
    // Get the Gadgeteet Socket for the specified socket number
    _socket = Socket.GetSocket(socketNumber, true, this, null);

    // Define two GPIO pins and set their initial state to false
    _focus = new DigitalOutput(_socket, Socket.Pin.Three, false, this);
    _shutter = new DigitalOutput(_socket, Socket.Pin.Four, false, this);
}


This is where we wire up the pins to the code. We can turn on or off a GPIO pin by calling the Write method of the pin and passing true or false to set the state, and we can implement a method for taking a picture with the following code:

public void TakePicture(int shutterTime)
{
    // Tell the camera to focus
    SetFocus(true);

    // wait 1/10 of a second
    Thread.Sleep(100);

    // Tell the camera to open the shutter
    SetShutter(true);

    // Hold the shutter open for the specified time
    if (shutterTime > 0)
    {
        Thread.Sleep(shutterTime);
    }

    // Let go of the shutter
    SetShutter(false);

    // let go of the focus
    SetFocus(false);
}

The main execution block is in the Program partial class, Program.cs. Create an instance of ShutterControl with the following code:

ShutterControl shutterControl = new ShutterControl(5);

That gives a ShutterControl object bound to socket 5. Then, taking a picture is as simple as calling:

shutterControl.TakePicture(1000);

Bluetooth (on the Gadgeteer side)

So why Bluetooth when all the cool kids are using Wi-Fi? There are a couple of reasons:

  • It’s cheaper. The Bluetooth Gadgeteer module is half the cost of the cheapest Wi-Fi module.
  • It uses less power. There’s no point in draining your phone’s battery any faster than you have to.
  • It’s much, much easier to configure. At the time of this writing, you cannot make the Gadgeteer the host in an Adhoc Wi-Fi network. That means it has to connect to an existing network. This opens up a box filled with ugly things. For example, there is neither a display nor a keyboard for this device, and so the user has no way of selecting a Wi-Fi network to join or entering a WEP or WPA key.

The Gadgeteer Bluetooth module supports the Serial Port Profile (SPP). Basically, it’s treated like a modem. When the app starts up, we call an InitBluetooth() method:

void InitBluetooth()
{
    // Keep a quick reference to the Bluetooth client, to make it
    // easier to send commands back over the module
    client = bluetooth.ClientMode;

    // Set the Bluetooth device name to make it easier to identify when
    // you pair this module with your phone
    bluetooth.SetDeviceName("Coding4Fun Pano Head");

    // We want to track when we are connected and disconnected
    bluetooth.BluetoothStateChanged += new Bluetooth.BluetoothStateChangedHandler(bluetooth_BluetoothStateChanged);

    // We want to collect all the data coming in
    bluetooth.DataReceived += new Bluetooth.DataReceivedHandler(bluetooth_DataReceived);
}

 

Command Parser

A simple command parser is implemented in CommandParser.cs. All of the data that comes in from the Bluetooth module gets appended to a custom Queue() class defined in CommandQueue.cs. A background thread calls the parser, which looks for command strings. Every command string starts with the “@” character and ends with a “#” character. The CommandParser.parse() goes through the buffer and queues up commands in the order they came in. The commands recognized by the app are defined as string constants in commands.cs.

The CheckForCommands() method is called after parsing the data and takes action for each command that comes in:

private void CheckForCommands()
{
    if (_commandParser.Commands.Count > 0)
    {
        ControlCommand controlCommand = _commandParser.Commands.Dequeue();
        switch (controlCommand.Action)
        {
            case Commands.CMD_LEDS:
                SpinLights();
                _client.Send("OK");
                break;

            case Commands.CMD_VERS:
                SendVersion();
                break;

            case Commands.CMD_STOP:
                _haltRequested = true;
                _servoControl.Stop();
                break;

            case Commands.CMD_RIGHT:
                _servoControl.ClockWise(_servoControl.Speed);
                break;

            case Commands.CMD_LEFT:
                _servoControl.ClockWise(-_servoControl.Speed);
                break;

            case Commands.CMD_SHUT:
                _shutterControl.TakePicture();
                break;

            case Commands.CMD_PANO:
                var p = new PanoSettings();
                p.Load(controlCommand.Params);
                PanoramaOperation(p);
                break;
        }
    }
}

 

To take a picture, the sender sends the start of command character, “@”, following by the CMD_SHUT string, and finally the end of command character, “#”. As a single string, it‘s “@SHUT#”.

Joystick processing

The joystick that comes with the kit has a button click event and you can poll it for the current position. I used the command sequence of a double click, followed by a direction. Double-click and up will put the Bluetooth module into pairing mode so that your phone can be paired to the device. A second double-click and up will reset the Bluetooth module to cancel pairing mode. Double-click and either left or right will rotate in that direction until the joystick is moved to another position. Double-click and down will put the servo into calibrate mode until the direction changes. The sequence is to double-click the joystick, then pick a direction. I coded the following functions:

Joystick Up: Puts the Bluetooth module into pairing mode. You need to do this once with the phone, to pair the phone with the device. Double-click, then Up will cancel pairing mode.

Joystick Left: Tells the servo to rotate left at the default speed. Continues until joystick changes direction.

Joystick Right: Tells the servo to rotate right at the default speed. Continues until joystick changes direction.

Joystick Down: Sends the 0 rotation signal to servo to allow you to set the set point of the servo. This is how you will calibrate the servo. Continues until joystick changes direction.

private void CheckJoyStick()
{
    if (_joyStickCommand.CheckState() == JoyStickCommandState.DoubleClicked)
    {
        var direction = _joyStickCommand.GetDirection(joystick.GetJoystickPosition());
        switch (direction)
        {
            case JoyStickDirection.Right:
                _servoControl.ClockWise(_servoControl.Speed);
                // turn to the right until the joystick is moved to a different direction
                while (direction == JoyStickDirection.Right)
                {
                    Thread.Sleep(300);
                    direction = _joyStickCommand.GetDirection(joystick.GetJoystickPosition());
                }
                _servoControl.Stop();
                break;

            case JoyStickDirection.Left:
                _servoControl.ClockWise(-_servoControl.Speed);
                // turn to the right until the joystick is moved to a different direction
                while (direction == JoyStickDirection.Left)
                {
                    Thread.Sleep(300);
                    direction = _joyStickCommand.GetDirection(joystick.GetJoystickPosition());
                }
                _servoControl.Stop();
                break;

            case JoyStickDirection.Up:
                if (!_inPairingMode)
                {
                    _client.EnterPairingMode();
                }
                else
                {
                    _bluetooth.Reset();
                }
                _inPairingMode = !_inPairingMode;
                break;

            case JoyStickDirection.Down:
                _servoControl.Calibrate();
                while (direction == JoyStickDirection.Down)
                {
                    Thread.Sleep(300);
                    direction = _joyStickCommand.GetDirection(joystick.GetJoystickPosition());
                }
                _servoControl.Stop();
                break;
        }

        if (direction != JoyStickDirection.None)
        {
            _joyStickCommand.ClearState();
        }
    }
}

 

Calibrating the servo

You can use the screwdriver included in the Parallax kit to turn the adjustment potentiometer on the servo. Put the device into calibrate mode via the joystick. Then, slowly turn the potentiometer in one direction. If the servo slows down, keep turning in that direction until the servo stops. If the servo starts going faster, then turn the potentiometer the opposite direction until the servo stops turning. If you turn the potentiometer past the zero point, the servo will start to spin in the opposite direction. If this happens, just turn the potentiometer very slowly in the other direction; it may take several tries to get it just right.

While writing this code, I managed to brick the board. I wrote some code to calibrate the servo and it used a while forever loop that was called when the device restarted. This eventually threw an error and prevented me from updating the app. I was forced to wipe the firmware and start from scratch. For the FEZ Cerberus board, the instructions are here: http://wiki.tinyclr.com/index.php?title=Firmware_Update_FEZ_Cerberus

Building the pano head

 

Cut two pieces of wood. The bottom piece is sized to match the Lazy Susan swivel, which is 6” square. Measure and mark the center of this board on both the top and bottom sides. Attach the bottom of the Lazy Susan to the top side of the bottom piece. Then, mount the larger reduction gear to the center of the top side of the bottom board:

image

Mount a tripod socket to the center of the bottom side of the board:

image

Mount the smaller reduction gear to the servo motor. This will replace the horn that comes with the servo:

image

Cut the top piece. This will be a little larger—I made a 10” by 7” rectangle. Drill the holes to mount it to the swivel and then mount the swivel just long enough to mark its center. Make sure that the two boards spin freely with the swivel unit. The first swivel that I used would bind up after 270 degrees of rotation. A replacement unit spun freely.

Next, cut a rectangular opening of about 1” by 3” in the top of the board, with one end over the center of swivel. This will allow the servo to be positioned in the hole so that the gears match up.

Secure the top board to the swivel unit:

image

Position the servo so that its gear is flush against the reduction gear. Getting the correct fit is important in order for the pano head to reliably turn. I used metal mounting strips from the hardware store and was able to secure the servo with two sets on either end sandwiching the servo mounts:

image

Now we have to mount the Gadgeteer parts and provide a camera mount. I took the low road here and picked up a pair of PVC wall boxes for electrical outlets from the hardware store. I placed them on opposite sides of the servo, secured the various Gadgeteer modules to one box, and left the other one open to hold the battery back. For ease of assembly, the extender module was not yet connected:

image

image

image

Connect the jumper wires to the camera shutter board and extender module and connect the jumpers to the servo connectors.

The camera board will connect the following lines to the extender module:

Pin 1 (shutter)

Pin3 (GPIO)

Pin 2 (focus)

Pin 4 (GPIO)

Pin 4 (ground)

Pin 10 (GRD)

The servo will connect the following lines to the extender module:

Positive (red)

Pin 2 (5v)

Control (white)

Pin 7 (PWM)

Ground (black)

Pin 10 (GRND)

The ground line of the extender module is shared between the camera control and the servo. Connect the extender module to socket 5 of the Cereberus mainboard.

 

For the camera mount, I took the low road again and cut a section from a yard stick to bridge the tops of the electrical boxes. In the center, I drilled ¼” hole and put a ½” thumbscrew to fit the tripod mount.

At this point, you can use the joystick to test various functions of the hardware. Make sure that everything works before you tackle the Bluetooth remote control:

image

 

Windows Phone 8 App

Now that the Pano Head has been built, we need the remote control—a Windows Phone 8 device, I used my HTC 8X, but any WP8 device should work. As regards the compiler, we need to switch gears. While the Gadgeteer library required Visual Studio 2010, you need Visual Studio 2012 and Windows 8 for Windows Phone 8 development. Windows 8 Pro is required for the Windows Phone emulator, but that is moot in this case. Since you can’t emulate Bluetooth connectivity in the emulator, you’ll need to have an actual WP8 phone.

I used the Windows Phone Pivot App template, but ended up ditching the pivot control as the app really only has a single page. To use Bluetooth, I need to add the ID_CAP_PROXIMITY and ID_CAP_NETWORKING capabilities to the app manifest. The completed app has two pivot pages and they look like this:

image

The Settings page, showing the various controls

About the icons in the apps

The app bar icons were generated with Syncfusion’s Metro Studio 2. Metro Studio 2 contains a collection of 1700 “Metro” style icon templates that be used to create customized icons for Windows Store Apps and Windows Phone Apps. Metro Studio 2 is a free gift from Syncfusion and the icons provided are royalty free without any usage restrictions.

The app icon that appears on the phone was derived from a camera image from the Open Clipart Library. The image that I used was a basic camera on a tripod image that works well with Windows Store and Windows Phone Store designs:

image 

Using the app

The panoramic setup takes 5 controls:

Turn time

How long to run the servo motor to make the turn.

Settle time

How long to wait after turning before taking the picture.

Pause time

How long to wait after taking the picture before turning again.

Iterations

How many times to turn.

Go!

Send the command to the Pano Head to start taking panoramic pictures.

 

The times are measured in milliseconds.

The app bar has two modes: connected and disconnected. When disconnected, the only button available is the connect button. All other controls are disabled until a connection is made. When there is a connection, 4 buttons and a menu option appear on the app bar:

Take picture

How long to run the servo motor to make the turn.

Turn left

Turn the Pano Head in a counter-clockwise direction until the stop button is pressed.

Stop

Tells the servo motor to stop turning.

Turn Right

Turn the Pano Head in a clockwise direction until the stop button is pressed.

Disconnect

Clear the Bluetooth connection to the Pano Head.

 

The IntegerTextBox custom control

The panoramic setting controls use sliders and text boxes to change the settings. We are following a MVVM pattern and the sliders and text boxes are bound to view mode properties. When you change a slider, the associated text box is updated and vice versa.

For the text boxes, I made them accept only integer inputs. I also made them right-aligned, as that is what most people expect for numeric input controls. To make the process easier, I created a new IntegerTextBox class based on the standard TextBox.

I created a Windows Phone Class Library with just the IntegerTextBox class. Basically, I made three changes to the behavior of the TextBox. The popup keyboard is set to the Digits keyboard, TextAlignment is set to TextAlignment.Right, and OnKeyDown is overridden in order to only allow the digit keys and the backspace key. It all comes out like this:

public class IntegerTextBox: TextBox
{
    private readonly Key[] _num = new Key[] {Key.Back, Key.D0, Key.D1, Key.D2, Key.D3, Key.D4,
    Key.D5, Key.D6, Key.D7, Key.D8, Key.D9 };

    public IntegerTextBox()
    {
        InputScope = new InputScope();
        InputScope.Names.Add(new InputScopeName { NameValue = InputScopeNameValue.Digits });
        TextAlignment = System.Windows.TextAlignment.Right;
    }

    protected override void OnKeyDown(KeyEventArgs e)
    {
        if (Array.IndexOf(_num, e.Key) == -1)
        {
            e.Handled = true;
        }
        base.OnKeyDown(e);
    }
}

Once the custom control project compiles, add that project as a reference to the phone project. Then add the namespace to the MainPage.xaml so that you can use the new control:

<phone:PhoneApplicationPage xmlns:PhoneControls="clr-namespace:Coding4Fun.PhoneControls;assembly=Coding4Fun.PhoneControls"
x:Class="Coding4Fun.PanoHead.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">

Settings

Settings are persisted to IsolatedStorageSettings.ApplicationSettings. I used the sample code posted on MSDN, “How to create a settings page for Windows Phone”, to create a class with accessor functions in order for all of the properties to persist.

Hurray for Bluetooth

Microsoft greatly enhanced the API access to the Bluetooth hardware with Windows Phone 8, extending the Windows 8 StreamSocket and Peerfinder APIs to include Bluetooth. With this, you get TCP-style socket level programming over Bluetooth.

At first glance, it would appear that the lack of a SPP API would make it difficult to have a WP8 device talk to the Pano Head. It turns out that’s not the case—we can use a socket connection from the phone to send and receive data from the Pano Head.

The PeerFinder class was designed to allow Windows Store apps to discover other instances of the app on other devices. But it can also be used to locate other devices once they have been paired to your phone. The way to do this is to add “Bluetooth:PAIRED” to the PeerFinder.AlternateIdentities list. That tells PeerFinder to include the list of already paired devices when searching for matching apps. We can then find the Pano Head in the list of paired devices:

private async Task<HostName> QueryForHost()
{
    // Tell PeerFinder to only enumerate paired devices
    // See http://msdn.microsoft.com/en-us/library/windowsphone/develop/jj207007(v=vs.105).aspx#BKMK_Codeexamples
    PeerFinder.AlternateIdentities["Bluetooth:PAIRED"] = "";
    IReadOnlyList<PeerInformation> devices = null;

    try
    {
        // Get a list of matching devices
        devices = await PeerFinder.FindAllPeersAsync();
    }
    catch{}

    // If we can't find any devices, goto to the phone's Bluetooth settings
    if (devices == null || devices.Count == 0)
    {
        MessageBox.Show(AppResources.NoBlueToothDevices);
        await Windows.System.Launcher.LaunchUriAsync(new Uri("ms-settings-bluetooth:"));
        return null;
    }

    // Find the first device that identifies as the Pano Head, otherwise go to the Bluetooth settings
    PeerInformation peerInfo = devices.FirstOrDefault(c => c.DisplayName.Contains("Pano Head"));
    if (peerInfo == null)
    {
        MessageBox.Show(AppResources.NoPairedPanos);
        await Windows.System.Launcher.LaunchUriAsync(new Uri("ms-settings-bluetooth:"));
        return null;
    }

    // Store the device name so the next connection attempt will use that first
    saveString("lastdevice", peerInfo.HostName.RawName);

    // Return the hostname
    return peerInfo.HostName;
}

 

Once we find the Pano Head, we open a StreamSocket connection to the device. The Pano Head sees it as an SPP connection on its side, while on the phone-side it’s TCP -style socket coding. Once we have established the connection, it’s a straight forward task to send commands from the phone to the Pano Head.

The socket code is wrapped up inside a PanoControl class. We create an instance of a DataWriter on the socket’s OutputStream, and to send a command to the Pano Head we just have to write a string using the DataWriter’s WriteString method. For example, to tell the Pano Head to take a picture, implement a TakePicture() method like so:

public async Task TakePicture()
{
    await SendCommand(Commands.CmdShut);
}

public DataWriterStoreOperation SendCommand(string command)
{
    CheckForDevice();
    _dataWriter.WriteString(String.Format("@{0}#", command));
    return _dataWriter.StoreAsync();
}

 

Though we use the same string constant file from the Gadgeteer project, that’s pretty much the only code shared between the two projects.

Using the Pano Head

Taking the pictures is just a matter of finding the right location—one with the sun overhead and a stable location for the tripod. Since you are shooting a full 360 degrees, if the sun is lower in the sky, the camera will at some point be aimed at it and it will blow out the image.

Once you have the camera in the right spot, it’s time to try a few panoramic shots. You want to have overlapping shots, and I found that 16 pictures taken at 20-degree intervals worked for my camera. You’ll have to experiment a bit with the turn time setting to get that 20 degree turn. Remember to allow a couple of seconds for the settle and pause times, giving the Pano Head enough time to settle after turning as well as enough time for your camera to focus and take the picture.

There are various ways of stitching multiple images together to make up a panoramic picture. Microsoft has excellent tool with their Photosynth app and site. You can create a panoramic picture with their desktop app and upload to their site for free hosting.

You will need two desktop apps, the Image Composite Editor (ICE) and Photosynth. ICE is the application that will stitch the Images together into a panorama and Photosynth will publish the panorama to the Photosynth site. Register an account on the Photosynth site and download the two applications here.

Creating a panoramic picture using Photosynth

  1. Take the pictures. You want some overlap, which will make it easier to stitch the images together.
  2. Copy the pictures to your computer.
  3. Launch Photosynth. If launched for the first time, the app will ask for Windows Live ID. Use the same account that you registered with on the Photosynth site. Once it comes up, you can ignore it.
  4. Launch ICE.
  5. Load your images into ICE. ICE will immediately start compositing the images. If you do not have sufficient overlap with the images, ICE will not be able to use all of the images.
  6. Click the “Publish to Web…” button. ICE will generate the panorama and use Photosynth to upload the image.
  7. Fill out the details when the “Upload Panorama” dialog appears and click the “Upload” button.
  8. Photosynth will upload the panorama. When it’s complete, click the “view” on the Photosynth window.

My Sample

I tested the Pano Head at a local nature preserve. After some trial and error, I took a series of 16 pictures that yielded a full 360-degree view. I copied the pictures from my camera to my PC and loaded them into ICE. Here are the results:

image

Conclusion

This project was a lot of fun to build. Since I had never soldered anything before, it helped me develop a new skill set. I went to RadioShack and bought some components and a small circuit board to practice on. While it is possible to learn how to solder via YouTube and Twitter, it was tough going at first:


image

I had a lot of trouble getting the solder to stick to the board on my first attempt. It was a facepalm moment.

Needless to say, I did get a bit better at soldering.

This was also my first Windows Phone 8 project. It was very easy to write for and a breeze to debug. After doing some work on other mobile platforms, I felt much more productive with Windows Phone 8.

If you want to try this out, the download link for the source code is at the top of the article!

About The Author

Chris Miller is a senior R&D engineer for Tyler Technologies, working on the next generation of school bus routing software. He is also the leader of the Tech Valley .NET Users Group (TVUG). You can follow Chris at @anotherlab and check out his blog at anotherlab.rajapet.net. Chris started out with a VIC-20 and been slowly moving up the CPU food chain ever since.

Follow the Discussion

Comments Closed

Comments have been closed since this content was published more than 30 days ago, but if you'd like to continue the conversation, please create a new thread in our Forums,
or Contact Us and let us know.