Wednesday, February 28, 2018

Windows IoT - Another UI "Hat"

I'm going to start by saying I really like "The Pi Hut", they do not pay me or send me free product or anything. I like the products and service. I live in the western US and their packages arrive from the UK in about a week via USPS, which is the same amount of time a certain brown carrier promises for 3 times the price and half the distance. I also got tired of watching my packages, sit at a brown distro center for a few days.  Much of the delivery kudos go to the USPS, which is my preferred carrier in general.

This time I'm going to share about the "ZeroSeg".

The ZeroSeg hat is a kit that does require soldering, it's all through hole and goes pretty quick but read the directions since there are couple of places that need to be done correctly. 

The basics for this hat are:
  • 8 qty 7-Segment characters (good for numbers and a few standard letters)
    • A few built in Characters are: 
      • H - High
      • L - Low
      • E - Error
      • P - Programming?
    • Characters can also be managed in software
    • 8 decimal dots
  • 2 buttons (that are on good pins and work great)
I probably will use this board on my outdoor robot, while it may be a little hard to read in bright sunlight, adding a little "sun visor" over it will help.

The Windows IoT perspective on this hat was a bit different, my first use of SPI communication. Also the first time I was required to use the async and await keywords. To be clear I've been a professional business application developer with .NET for 13 years now, I've used BackgroundWorkers, and Threads and on a few occasions Application.DoEvents() to keep my UI responsive. In concept the async and await are a nice easy way to send a request and wait for the response. In practice I was very frustrated first by the constraints of the concept and then by the fact that I couldn't do standard synchronous SPI communication. 

To be clear I'm still learning async & await so there are probably solutions to my complaints. In my experience I couldn't use this concept inside a property body or in a class constructor. Sure I would probably agree that those are the wrong places to use it, but as I said I was forced to use async/await because there isn't a synchronous api available for creating an SpiDevice.

My plan was to create a wrapper class to abstract away some of the "bit-twiddling" and "register-stuffing" involved in using the MAX7219. The first step was to setup the Spi device in the class constructor. Well constructors can't use the async keyword. So, I made another method "ConnectSPI" that has to be called after the constructor before you can use the class. The next problem was ConnectSPI was returning to the main code before it had completed. Well the next lines were the configuration lines I used to setup the display how I wanted it vs the default configuration. These lines would then fail because they needed to communicate over the SPI connection which wasn't ready. I feel like it's a hack but I created an IsReady property that gets set when the ConnectSPI method completes, and my main code has a while statement that spins until it's Ready. Overall not my proudest code, but it works.

The simplest usage of my class looks like this:
            ZeroSeg seg = new ZeroSeg();
            seg.ConnectSPI();
            while (!seg.IsReady){}
            seg.SetText("01234567");

There is a lot more opportunity to create a full 7-Segment Character set, but I decided to stop at simple. Feel free to modify the code to suit your needs. 

Tuesday, February 27, 2018

Windows IoT - Status Board

Windows IoT - Status Board

I found a really cool "Hat" for the Raspberry Pi on https://thepihut.com called "Status Board" There is a 5 line version for Pi2/3 and a 3 line version for the Pi Zero. It's fairly simple, uses just the GPIO so I thought it would be a good place to write some code for Windows IoT.

The expectation is that most users will just use the Python library that is available, so there isn't much technical documentation. On the back side of the board the GPIO are listed by function "Breakout", "Button" and "LED" that's literally it. For example it doesn't specify which pin for which line or red/green. Also no mention of Hi/Lo Pullup/Pulldown etc. This doesn't make the job impossible, but it would of been nice.

Nuances\Gotchas:

  • Pin 14, 15 are reserved in Windows IoT but are used so line 1 and 3 buttons don't work.
  • Pin 4, 17 doesn't work like I expected, so line one LED's don't work (red or green)
  • Pin 19 doesn't work like I expected, so line 2 button doesn't work. 
To be fair the issues on Pins 4, 17, 19 could be something wrong with my solder work or an issue on the Pi2 that I'm using, so I left the appropriate code for these functions, it just doesn't do anything for me.

Result was a little disappointing:

Line one: Nothing works
Line two: LED's work, No button
Line three: LED's work, No button
Line four: LED's work, Button works
Line five: LED's work, Button works

Personally I probably won't use these boards for my robot project, the LED's are "indoor" bright but my robot is intended for outdoor use.

I've attached my source code below which may be useful for someone to learn from.

Here is an example of how to use it:

            PiStatus piStatus = new PiStatus("Testing", 5);
            piStatus.Buttons[1].ValueChanged += StartupTask_ValueChanged; // Doesn't work for me
            piStatus.Buttons[3].ValueChanged += StartupTask_ValueChanged;
            piStatus.Buttons[4].ValueChanged += StartupTask_ValueChanged;
            piStatus.SetLED(0, LEDColor.Green); // Doesn't work for me
            piStatus.SetLED(1, LEDColor.Green);
            piStatus.SetLED(2, LEDColor.Green);
            piStatus.SetLED(3, LEDColor.Green);
            piStatus.SetLED(4, LEDColor.Green);
            piStatus.SetLED(0, LEDColor.Off);
            piStatus.SetLED(1, LEDColor.Off);
            piStatus.SetLED(2, LEDColor.Off);
            piStatus.SetLED(3, LEDColor.Off);
            piStatus.SetLED(4, LEDColor.Off);
            piStatus.SetLED(0, LEDColor.Red); // Doesn't work for me
            piStatus.SetLED(1, LEDColor.Red);
            piStatus.SetLED(2, LEDColor.Red);
            piStatus.SetLED(3, LEDColor.Red);
            piStatus.SetLED(4, LEDColor.Red);

Friday, February 23, 2018

Windows IoT on Raspberry Pi - GPIO

Understanding which GPIO are available.

Reference Doc for this post: Windows IoT - Raspberry Pi I/O

The above link has the basic of what it takes to write an a program that uses GPIO. In my case I wanted to reverse engineer some simple add-on boards aka hats. A quick reminder that Windows IoT actually runs on multiple hardware platforms so there are some features that don't work on the Raspberry Pi. So even if you are using a completely different board than the Raspberry Pi this post will be helpful.

The code for this post will exist in the main class file and I don't want this to be a screenshot step by step how to, I'll cover some of the differences and why.

New "Background Application (IoT) - Visual C#". If this is a personal project always pick the latest for both Min and Max target version. If you were making a commercial product and had to support existing installations the Min version would be useful.

I'll provide the code as snippets inline and the full code sample at the end of this post.

The first piece to understand is that Windows IoT is a multitasking operating system and have your code do a classic for(;;){} loop would not be playing nice with other programs. With out the following lines of code, your program will run and when the steps are done, it will close. Adding these lines tell it to remain running and it will then continue to use event handlers to respond to events.

private BackgroundTaskDeferral deferral;

public void Run(IBackgroundTaskInstance taskInstance)
{
deferral = taskInstance.GetDeferral();
}

The next thing we need is to learn about the pins that we have available, unlike other platforms were everything is in the datasheet in this environment we can loop through the I/O and query it for attributes.
        private void GpioConfiguration()
        {
            gpio = GpioController.GetDefault();
            int[] pins = new int[30];

            for (int idx = 0; idx < 30; idx++)
            {
                pins[idx] = idx;
            }

            pins[28] = 35; // RPi2 Only
            pins[29] = 47; // RPi2 Only

            foreach (var p in pins)
            {
                GpioOpenStatus openStatus;
                List<GpioPinDriveMode> supported = new List<GpioPinDriveMode>();
                if (gpio.TryOpenPin(p, GpioSharingMode.SharedReadOnly, out pin, out openStatus))
                {
                    foreach (GpioPinDriveMode driveMode in Enum.GetValues(typeof(GpioPinDriveMode)))
                    {
                        // test driveMode for Pin Details
                        if (pin.IsDriveModeSupported(driveMode))
                        {
                            supported.Add(driveMode);
                        }
                    }
                }
                
                Debug.WriteLine("GPIO: {0} Status: {1}   Supported Drive Modes: {2}", p, openStatus, (supported.Count == 0)? "0" : string.Join(",", supported));
            }

        }


Adding a few class variable declarations (see full code at the bottom). Then calling this method right after our "GetDeferral();" code will give us the overview on the I/O pins by using the debugger to view the 'results' variable.

Not to spoil anything but the highlights are:

  • 0, 1 are PinUnavailable
  • 14,15 are PinUnavailable
  • All pins support the following drive modes
    • Input (Hi-Z)
    • Output
    • InputPullUp
    • InputPullDown
The Details: 
GPIO: 0 Status: PinUnavailable   Supported Drive Modes: 0
GPIO: 1 Status: PinUnavailable   Supported Drive Modes: 0
GPIO: 2 Status: PinOpened   Supported Drive Modes: Input,Output,InputPullUp,InputPullDown
GPIO: 3 Status: PinOpened   Supported Drive Modes: Input,Output,InputPullUp,InputPullDown
GPIO: 4 Status: PinOpened   Supported Drive Modes: Input,Output,InputPullUp,InputPullDown
GPIO: 5 Status: PinOpened   Supported Drive Modes: Input,Output,InputPullUp,InputPullDown
GPIO: 6 Status: PinOpened   Supported Drive Modes: Input,Output,InputPullUp,InputPullDown
GPIO: 7 Status: PinOpened   Supported Drive Modes: Input,Output,InputPullUp,InputPullDown
GPIO: 8 Status: PinOpened   Supported Drive Modes: Input,Output,InputPullUp,InputPullDown
GPIO: 9 Status: PinOpened   Supported Drive Modes: Input,Output,InputPullUp,InputPullDown
GPIO: 10 Status: PinOpened   Supported Drive Modes: Input,Output,InputPullUp,InputPullDown
GPIO: 11 Status: PinOpened   Supported Drive Modes: Input,Output,InputPullUp,InputPullDown
GPIO: 12 Status: PinOpened   Supported Drive Modes: Input,Output,InputPullUp,InputPullDown
GPIO: 13 Status: PinOpened   Supported Drive Modes: Input,Output,InputPullUp,InputPullDown
GPIO: 14 Status: PinUnavailable   Supported Drive Modes: 0
GPIO: 15 Status: PinUnavailable   Supported Drive Modes: 0
GPIO: 16 Status: PinOpened   Supported Drive Modes: Input,Output,InputPullUp,InputPullDown
GPIO: 17 Status: PinOpened   Supported Drive Modes: Input,Output,InputPullUp,InputPullDown
GPIO: 18 Status: PinOpened   Supported Drive Modes: Input,Output,InputPullUp,InputPullDown
GPIO: 19 Status: PinOpened   Supported Drive Modes: Input,Output,InputPullUp,InputPullDown
GPIO: 20 Status: PinOpened   Supported Drive Modes: Input,Output,InputPullUp,InputPullDown
GPIO: 21 Status: PinOpened   Supported Drive Modes: Input,Output,InputPullUp,InputPullDown
GPIO: 22 Status: PinOpened   Supported Drive Modes: Input,Output,InputPullUp,InputPullDown
GPIO: 23 Status: PinOpened   Supported Drive Modes: Input,Output,InputPullUp,InputPullDown
GPIO: 24 Status: PinOpened   Supported Drive Modes: Input,Output,InputPullUp,InputPullDown
GPIO: 25 Status: PinOpened   Supported Drive Modes: Input,Output,InputPullUp,InputPullDown
GPIO: 26 Status: PinOpened   Supported Drive Modes: Input,Output,InputPullUp,InputPullDown
GPIO: 27 Status: PinOpened   Supported Drive Modes: Input,Output,InputPullUp,InputPullDown
GPIO: 35 Status: PinOpened   Supported Drive Modes: Input,Output,InputPullUp,InputPullDown
GPIO: 47 Status: PinOpened   Supported Drive Modes: Input,Output,InputPullUp,InputPullDown

Other testing has shown the following pins not working as expected 4, 17, 19. At this time I'm not sure why, it could easily be a solder short or other damage to those pins on my RPi2.

Complete Source Code

Windows IoT on Raspberry Pi

As time moves on so do my interests. 

The Raspberry Pi is a very popular platform, but Python isn't the right language for me. I write most of my personal projects in C#, for the productivity and debugging capabilities. The question is do the benefits of C# translate over to the Raspberry Pi?

To be clear the following experience is mostly with a Raspberry Pi 2, Visual Studio 2017, Target Version "Windows 10 Fall Creators Update (10.0; Build 16299"), wired network connection.

If this project works out I'll use the Raspberry Pi with Windows IoT as a robot controller. If it doesn't work out I'll probably use a different hardware platform to control the robot, since I don't want to write and "debug" that much Python code.

The best reference for me has been this Microsoft Windows IoT Raspberry Pi page.

 It seems there are lots of random "gotchas" most aren't bad, in fact I haven't found a "deal breaker" yet, the first ones I ran into:

  • - Windows IoT doesn't run on the Raspberry Pi Zero or Zero W
    • Zero is Arm6 and WinIoT is compiled for Arm7 and above
  • - Several GPIO are unavailable (0, 1, 14, 15)
    • 0, 1 are EEPROM Id pins and shouldn't really be "re-used".
    • 14,15 are a dedicated USART and can't be "re-used".
Good news is that the Raspberry Pi 2 (RPi2) works and Raspberry Pi 3 (RPi3) support seems good.
  • - To setup Wireless for the RPi3, requires running on a PC that is connected to that Wireless network, i.e. not a wired desktop. I haven't seen a manual configuration option.
C# Language and Debugging on Windows IoT / Rasperry Pi
C# programming has some differences, it's an embedded platform so there are new Namespaces to support new features. There are also new concepts like the IBackgroundTask that take some getting used too. I haven't found any missing features, in fact I found a use for a Tuple and it worked as expected, basically I used it as a "lazy class" I wanted to collect Pin Data in a structured way and didn't have to make a 3 property struct/class to store the data.

I'm very happy that even the Resharper Visual Studio Add-In is working great in these UWP projects.

Debugging has a little more of an up and down story. When it's good it's very good, when it's bad your reminded that the Raspberry Pi is the same size as a baseball and would be very satisfying to destroy.

The good part of the debug experience is the variable expansion, setting the next line to execute (forward or backward). I'm a little bummed that Edit and Continue doesn't work.

The bad part of the debug experience is the Raspberry Pi seems to go to sleep or fall of the network after some period of time. Once this happens it's trial and error to get the debugger connected again. I think I have some reliable steps but I'll test a few more times before publishing.

More to come!