Feather Powered Camera Pan Tilt

Yet another FEZ Feather project. What is it?

A camera pan tilt system built using a FEZ Feather for connectivity, a couple of Pololu Jrk motor controllers, and some parts from ServoCity. This is remotely controlled using a Xamarin application currently installed on my phone.

So how does it work?

The FEZ Feather runs a TCP socket server and accepts command from the android application. The Feather then transforms the appropriate pan and or tilt command, sending a move target to appropriate Jrk motor Controller. These Jrk motor controllers are communicated with via I2c and configured as closed loop. The Feather in turn captures the movement from the Jrk controllers and sending them back to the android app.

Android app to pan tilt camera

7 Likes

IMO, a cool part of the programming is how it connects to a WiFi network. There is a config.json file that gets loaded onto a flash drive then inserted into the Feather USB drive. Upon loading the drive, the config.json file gets parse into an object representation, at this the program looks to see if any WiFi network is present and tries to connect to it.

config.json

{
  "WifiNetworks": [
    {
      "Ssid": "Wifi Network A",
      "Password": "password"
    },
    {
      "Ssid": "Wifi Network B",
      "Password": "password"
    },
    {
      "Ssid": "Wifi Network B",
      "Password": "password"
    }
  ],
  "TimeZoneOffsetInMinutes": -300.0,
  "DaylightSavingsTimeOffsetInMinutes": 60.0
}

Configuration and WifiNetwork classes

 public class Configuration
    {
        public WifiNetwork[] WifiNetworks { get; set; }
        public double TimeZoneOffsetInMinutes { get; set; }
        public double DaylightSavingsTimeOffsetInMinutes { get; set; }

        public static object CreateInstance(string path, JToken token, Type type, string name, int length)
        {
            switch (name)
            {
                case "WifiNetworks":
                    return new WifiNetwork[length];
                default:
                    if (type == null && string.IsNullOrEmpty(name) && ((JObject)token).Contains("Ssid"))
                        return new WifiNetwork();
                    break;
            }
            return null;
        }

    }

public class WifiNetwork
    {
        public string Password { get; set; }
        public string Ssid { get; set; }
        public bool IsConnected { get; set; }
    }

WiFi initialization code:

 private static void InitializeWifi(GpioController gpioController)
        {
            var wifiEnablePin = gpioController.OpenPin(GHIElectronics.TinyCLR.Pins.SC20100.GpioPin.PA8);
            wifiEnablePin.SetDriveMode(GpioPinDriveMode.Output);
            wifiEnablePin.Write(GpioPinValue.High);

            var settings = new SpiConnectionSettings()
            {
                ChipSelectLine = gpioController.OpenPin(GHIElectronics.TinyCLR.Pins.SC20100.GpioPin.PD15),
                ClockFrequency = 4000000,
                Mode = SpiMode.Mode0,
                ChipSelectType = SpiChipSelectType.Gpio,
                ChipSelectHoldTime = TimeSpan.FromTicks(10),
                ChipSelectSetupTime = TimeSpan.FromTicks(10)
            };

            SpiNetworkCommunicationInterfaceSettings netInterfaceSettings = new SpiNetworkCommunicationInterfaceSettings()
            {
                SpiApiName = GHIElectronics.TinyCLR.Pins.SC20100.SpiBus.Spi3,
                GpioApiName = GHIElectronics.TinyCLR.Pins.SC20100.GpioPin.Id,
                SpiSettings = settings,
                InterruptPin = gpioController.OpenPin(GHIElectronics.TinyCLR.Pins.SC20100.GpioPin.PB12),
                InterruptEdge = GpioPinEdge.FallingEdge,
                InterruptDriveMode = GpioPinDriveMode.InputPullUp,
                ResetPin = gpioController.OpenPin(GHIElectronics.TinyCLR.Pins.SC20100.GpioPin.PB13),
                ResetActiveState = GpioPinValue.Low
            };
            var networkController = NetworkController.FromName("GHIElectronics.TinyCLR.NativeApis.ATWINC15xx.NetworkController");

            networkController.SetCommunicationInterfaceSettings(netInterfaceSettings);
            networkController.SetAsDefaultController();
            byte[] address = new byte[4];

            networkController.NetworkAddressChanged += (NetworkController sender, NetworkAddressChangedEventArgs e) =>
            {
                _line3 = "0.0.0.0";
                var ipProperties = sender.GetIPProperties();
                address = ipProperties.Address.GetAddressBytes();
                if (address[0] > 0)
                {
                    _line3 = $"{address[0]}.{address[1]}.{address[2]}.{address[3]}";
                    _line2 = $"{((WiFiNetworkInterfaceSettings)sender.ActiveInterfaceSettings).Ssid}";
                    GetNetworkTime();
                }

            };

            //Starting a new thread for monitoring the WiFi connection state.
            //Loop through a list of known networks from the configuration file 
            new Thread(() =>
            {
                int idx = 0;

                while (true)
                {
                    try
                    {
                        if (_configuration == null || _configuration.WifiNetworks.Length == 0)
                        {
                            Thread.Sleep(5000);
                            continue;
                        }

                        var network = _configuration.WifiNetworks[idx];
                        _line2 = "Connecting to AP";
                        _line3 = network.Ssid;

                        Thread.Sleep(1000);

                        try
                        {

                            WiFiNetworkInterfaceSettings wifiSettings = new WiFiNetworkInterfaceSettings()
                            {
                                Ssid = network.Ssid,
                                Password = network.Password,
                                DhcpEnable = true,
                                DynamicDnsEnable = true,
                                TlsEntropy = new byte[] { 0, 1, 2, 3 }
                            };
                            networkController.SetInterfaceSettings(wifiSettings);

                            //Try enabling WiFi interface
                            networkController.Enable();

                            _line2 = network.Ssid;

                            //Sleep for 5sec to give ample time fr wifi to connect
                            Thread.Sleep(2000);
                            network.IsConnected = true;
                            //Poll WiFi connection link every 5sec
                            while (networkController.GetLinkConnected())
                            {
                                Thread.Sleep(10000);
                            }
                        }
                        catch (ArgumentException ex)
                        {
                            //This is exception is thrown when failed to connect to network

                            _line2 = "Unable to connect to network";
                            Thread.Sleep(1000);
                            network.IsConnected = false;
                            idx++;
                            if (idx == _configuration.WifiNetworks.Length)
                                idx = 0;
                        }
                        catch (Exception ex)
                        {
                            _line2 = "Unable to connect to network";
                            network.IsConnected = false;
                            idx++;
                            if (idx == _configuration.WifiNetworks.Length)
                                idx = 0;
                            Thread.Sleep(1000);

                        }
                        networkController.Disable();
                    }
                    catch (Exception ex)
                    {
                        try
                        {
                            networkController.Disable();
                        }
                        catch (Exception)
                        {
                            //Intentionally left blank;
                        }
                    }

                }


            }).Start();
        }

USB host initialization code

private static void InitializeUsbHost()
        {
            //Initialize usb host controller
            GHIElectronics.TinyCLR.Devices.UsbHost.UsbHostController usbHost = GHIElectronics.TinyCLR.Devices.UsbHost.UsbHostController.GetDefault();
            usbHost.OnConnectionChangedEvent += (sender, e) =>
            {
                switch (e.DeviceStatus)
                {
                    case GHIElectronics.TinyCLR.Devices.UsbHost.DeviceConnectionStatus.Disconnected:
                        switch (e.Type)
                        {

                            case GHIElectronics.TinyCLR.Devices.UsbHost.BaseDevice.DeviceType.MassStorage:
                                if (_storageController != null)
                                    FileSystem.Unmount(_storageController.Hdc);
                                break;
                        }
                        break;
                    case GHIElectronics.TinyCLR.Devices.UsbHost.DeviceConnectionStatus.Connected:
                        switch (e.Type)
                        {
                            case GHIElectronics.TinyCLR.Devices.UsbHost.BaseDevice.DeviceType.MassStorage:

                                _storageController = StorageController.FromName(SC20260.StorageController.UsbHostMassStorage);
                                var driver = FileSystem.Mount(_storageController.Hdc);

                                var driveInfo = new DriveInfo(driver.Name);
                                if (File.Exists(Path.Combine(driveInfo.Name, "config.json")))
                                {
                                    _configuration = (Configuration)JsonConverter.DeserializeObject(System.Text.Encoding.UTF8.GetString(File.ReadAllBytes(Path.Combine(driveInfo.Name, "config.json"))), typeof(Configuration), factory: Configuration.CreateInstance);
                                }
                                _line2 = string.Empty;
                                _line3 = "Looking for a network";

                                break;
                        }
                        break;
                    case GHIElectronics.TinyCLR.Devices.UsbHost.DeviceConnectionStatus.Bad:
                        break;
                    default:
                        break;
                }
            };
            usbHost.Enable();
        }
6 Likes

What a great post. We need more of these. Thanks for sharing.

3 Likes

Just tried your solution on a SC20260 Board and the FEZ Duino and it works flawlessly :clap: :clap:

-GitHub - RoSchmi/SC20260_WiFi_init_via_UsbStick: Initialize TinyCLR Board's Wifi credentials via json file in USB-Stick

I have a question running the code on the FEZ-Feather:

Did you use a separate USB A connector (which type/brand) or did you use the USB C connector which is normally used for power and debugging?

And I have a proposal: Would it be possible to store the Credentials from the USB-Stick in the boards flash, so that the USB-Stick can be pulled after booting the first time? (So it could be prevented that someone steals the USB-Stick to easily read out the Wifi credentials).

I am glad that this solution was of benefit to you. On the Feather, I am using a breakout board connected to the USB pins and the flash drive is then inserted there. One problem that I have encountered, sometimes I will have to re-insert the external flash drive after a power up to get it initialized. This could be due to the fact is connected to the 3.3v power rail, but this it not an issue for me since re-inserting works just fine. As far as storing the credentials on the board after initialization, I will have to look into that portion; I surmised that this can be accomplished using secure storage.

2 Likes

Ah…, I remember that seemingly someone already did it:
-Memory Manager

Full disclosure. I’ve yet to test that lib with RTC memory as at the time the hardware I have didn’t yet supported it… :grin:

1 Like

Thanks for sharing, won’t reinvent the wheel and this library seems simple enough to use.

1 Like