Graham Chow
Published © MIT

PiFace Digital 2 Windows IOT driver (with events)

Drives the Pi Face Digital 2 from Raspberry Pi 2 (with events)

IntermediateFull instructions provided3,934
PiFace Digital 2 Windows IOT driver (with events)

Things used in this project

Hardware components

Raspberry Pi 2 Model B
Raspberry Pi 2 Model B
×1
pi face digital 2
×1

Software apps and online services

Windows 10 IoT Core
Microsoft Windows 10 IoT Core

Story

Read more

Schematics

PiFace Digital 2.png

Piface%20digital%202

Code

SPI Driver

C#
The guts of driving the PI Face's digital 2 board
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.Devices.Enumeration;
using Windows.Devices.Spi;
using Windows.Devices.Gpio;
using Windows.Graphics;

namespace PifaceDigital2
{

    public class PiFaceEventArgs : EventArgs
    {
        public byte PinMask { get; set; }
        public byte PinState { get; set; }
    }

    public delegate void PiFaceInputChange(object sender, PiFaceEventArgs e);

    public class PiFaceSpiDriver : IDisposable   
    {
        // notes reset on the MCP23S17 is tied to 3.3V
        // Block GPA is configured as inputs
        // Block GPB is configured as outputs
        // leave the IOCON.BANK = 0

        private GpioController IoController = null;
        private GpioPin InterruptPin = null;

        private const string SPI_CONTROLLER_NAME = "SPI0";  /* For Raspberry Pi 2, use SPI0                             */
        private const Int32 SPI_CHIP_SELECT_LINE = 0;       /* Line 0 maps to physical pin number 24 on the Rpi2        */
        private const Int32 INTERRUPT_PIN = 25;

        private const byte SPI_SLAVE_READ = 1;
        private const byte SPI_SLAVE_WRITE = 0;
        private const byte SPI_SLAVE_ID = 0x40;
        private const byte SPI_SLAVE_ADDR = 0; // 0, 1, 2

        private const byte IOCONA = 0xA;
        private const byte IODIRA = 0x00;
        private const byte IODIRB = 0x01;
        private const byte GPPUA = 0x0C;
        private const byte GPPUB = 0x0D;
        private const byte GPIOA = 0x12;
        private const byte GPIOB = 0x13;
        private const byte OLATA = 0x14;
        private const byte OLATB = 0x15;

        private const byte DEFVALB = 0x7;
        private const byte INTCONB = 0xB;
        private const byte GPINTENB = 0x5;
        private const byte INTFB = 0xF;
        private const byte INTCAPB = 0x11;

        private SpiDevice SpiDev = null;

        public event EventHandler<PiFaceEventArgs> PiFaceInterrupt;

        public async Task InitHardware()
        {
            IoController = GpioController.GetDefault();
            InterruptPin = IoController.OpenPin(INTERRUPT_PIN);
            InterruptPin.DebounceTimeout = new TimeSpan(0, 0, 0, 0, 50);
            InterruptPin.Write(GpioPinValue.High);
            InterruptPin.SetDriveMode(GpioPinDriveMode.Input);
            InterruptPin.ValueChanged += Interrupt;

            var settings = new SpiConnectionSettings(SPI_CHIP_SELECT_LINE);
            settings.ClockFrequency = 10000000;      // max frequency for MCSP23S17 is 10Mhz
            settings.Mode = SpiMode.Mode3;          // data read on the rising edge - idle high
            string spiAqs = SpiDevice.GetDeviceSelector(SPI_CONTROLLER_NAME);
            var devicesInfo = await DeviceInformation.FindAllAsync(spiAqs);
            SpiDev = await SpiDevice.FromIdAsync(devicesInfo[0].Id, settings);

            await Task.Delay(20);

            Write(IOCONA, 0x28); // BANK=0, SEQOP=1, HAEN=1 (Enable Addressing) interrupt not configured
            Write(IODIRA, 0x00); // GPIOA As Output
            Write(IODIRB, 0xFF); // GPIOB As Input
            Write(GPPUB, 0xFF); // configure pull ups

            Write(DEFVALB, 0x00); // normally high, only applicable if INTCONB == 0x0xFF
            Write(INTCONB, 0x00); // interrupt occurs upon pin change
            //Write(INTCONB, 0xFF); // interrupt occurs when pin values become DEFVALB values
            Write(GPINTENB, 0xFF); // enable all interrupts
            Write(GPIOA, 0x55); // drive all outputs low
        }



        private void Write(byte address, byte data)
        {
            if (SpiDev != null)
            {
                byte[] buffer = new byte[3];
                buffer[0] = SPI_SLAVE_ID | ((SPI_SLAVE_ADDR << 1) & 0x0E) | SPI_SLAVE_WRITE;
                buffer[1] = address;
                buffer[2] = data;
                SpiDev.Write(buffer);
            }
        }

        private byte Read(byte address)
        {
            if (SpiDev != null)
            {
                byte[] buffer = new byte[2];
                buffer[0] = SPI_SLAVE_ID | ((SPI_SLAVE_ADDR << 1) & 0x0E) | SPI_SLAVE_READ;
                buffer[1] = address;
                byte[] read_buffer = new byte[1];
                SpiDev.TransferSequential(buffer, read_buffer);
                return read_buffer[0];
            }
            return 0;
        }

        public void WriteGPA(byte value)
        {
            Write(GPIOA, value);
        }

        public byte ReadGPA()
        {
            return Read(OLATA);
        }

        public byte ReadGPB()
        {
            return Read(GPIOB);
        }

        private void Interrupt(GpioPin sender, GpioPinValueChangedEventArgs args)
        {
            if (PiFaceInterrupt != null)
            {
                PiFaceEventArgs pfea = new PiFaceEventArgs();
                pfea.PinMask = Read(INTFB); // find out what caused the interrupt
                pfea.PinState = Read(INTCAPB); // read the state of the pins
                PiFaceInterrupt(this, pfea);
            }
        }

        #region IDisposable Support
        private bool disposedValue = false; // To detect redundant calls

        protected virtual void Dispose(bool disposing)
        {
            if (!disposedValue)
            {
                if(SpiDev != null)
                    SpiDev.Dispose();
                if(InterruptPin != null)
                    InterruptPin.Dispose();
                disposedValue = true;
            }
        }

         ~PiFaceSpiDriver()
        {
            // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
            Dispose(false);
        }

        // This code added to correctly implement the disposable pattern.
        public void Dispose()
        {
            // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
            Dispose(true);
            GC.SuppressFinalize(this);
        }
        #endregion
    }
}

Main

C#
Use of the driver code
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;

// The Blank Page item template is documented at http://go.microsoft.com/fwlink/?LinkId=402352&clcid=0x409

namespace PifaceDigital2
{
    /// <summary>
    /// An empty page that can be used on its own or navigated to within a Frame.
    /// </summary>
    public sealed partial class MainPage : Page
    {
        PiFaceSpiDriver spi_driver = new PiFaceSpiDriver();
        private DispatcherTimer timer;

        public MainPage()
        {
            this.InitializeComponent();
            Init();
            timer = new DispatcherTimer();
            timer.Interval = TimeSpan.FromMilliseconds(100);
            timer.Tick += Timer_Tick;
            //timer.Start();

            spi_driver.PiFaceInterrupt += Spi_driver_PiFaceInterrupt;
        }

        private void Spi_driver_PiFaceInterrupt(object sender, PiFaceEventArgs e)
        {
            int state = spi_driver.ReadGPB();
            state &= ~e.PinMask;
            state |= e.PinState & e.PinMask;
            spi_driver.WriteGPA((byte)(~(state)));
        }

        private async void Init()
        {
            await spi_driver.InitHardware();
        }

        private void Timer_Tick(object sender, object e)
        {
            byte state = spi_driver.ReadGPB();
            Status.Text = state.ToString();
            state &= 0xAF;
            spi_driver.WriteGPA(state);
        }

    }
}

Credits

Graham Chow

Graham Chow

14 projects • 70 followers
I'm a software developer on a long sabbatical. I've had wide experience from banking to underwater military sonar systems.

Comments