File: PRUDriver

Details

File: PRUDriver.cs
Date: Thu, Mar 7, 2019
using System;
using System.IO;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
using Mono.Unix;
using Mono.Unix.Native;

/// +------------------------------------------------------------------------------------------------------------------------------+
/// |                                                   TERMS OF USE: MIT License                                                  |
/// +------------------------------------------------------------------------------------------------------------------------------|
/// |Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation    |
/// |files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy,    |
/// |modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software|
/// |is furnished to do so, subject to the following conditions:                                                                   |
/// |                                                                                                                              |
/// |The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.|
/// |                                                                                                                              |
/// |THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE          |
/// |WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR         |
/// |COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,   |
/// |ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                         |
/// +------------------------------------------------------------------------------------------------------------------------------+

namespace BBBCSIO
{
    /// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
    /// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
    /// <summary>
    /// Provides the functionality to interact with a PRU on the Beaglebone Black
    /// </summary>
    /// <history>
    ///    05 Jun 15  Cynic - Originally written
    /// </history>
    public class PRUDriver :  IDisposable
    {
        // for substitutions
        private const string PRUNUM = "%PRUNUM%";
        private const string PRUUIONUM = "%PRUUIONUM%";

        private const uint PRU_UIO_PAGE_SIZE = 4096;
        private const uint PRU_UIO_MMAP_BASE = 0 * PRU_UIO_PAGE_SIZE;
        // the size of the PRU's memory mapped into user space by the UIO routines
        private const uint PRU_UIO_PHYSMAPSIZE = 0x00080000;
        private const string PRU_UIODEVICE_NAME = "/dev/uio%PRUUIONUM%";
        private const string PRU_UIO_BASE_PATH = "/sys/class/uio/uio%PRUUIONUM%/maps/map0/addr";
        private const string PRU_UIO_SIZE_PATH = "/sys/class/uio/uio%PRUUIONUM%/maps/map0/size";

        // not needed
        //#define PRUSS_UIO_MAP_OFFSET_EXTRAM 1*PAGE_SIZE
        //#define PRUSS_UIO_DRV_EXTRAM_BASE "/sys/class/uio/uio0/maps/map1/addr"
        //#define PRUSS_UIO_DRV_EXTRAM_SIZE "/sys/class/uio/uio0/maps/map1/size"

        // the PRU we are using
        private PRUEnum pruID = PRUEnum.PRU_NONE;

        // this contains the file descriptors of events which are enabled and which
        // we are monitoring. The index into the dictionary is the event enum, the 
        // value associated with it is the file descriptor of the enabled event uio file
        Dictionary<PRUEventEnum, PRUEventHandler> enabledEvents = new Dictionary<PRUEventEnum, PRUEventHandler>();

        // ###
        // ### runtime discovered or calculated values
        // ###
        // the user space base address of the PRU system as mapped in by uio_pruss
        private uint pruPhysBaseAddr = 0;
        // the user space base address range of the PRU system as mapped in by uio_pruss
        private uint pruPhysMapSize = 0;

        // ###
        // ### Constants
        // ###
        private const uint PRUOFFSET_CONTROL_PRU0 = 0x00022000;
        private const uint PRUOFFSET_CONTROL_PRU1 = 0x00024000;
        private const uint PRUOFFSET_IRAM_PRU0 = 0x00034000;
        private const uint PRUOFFSET_IRAM_PRU1 = 0x00038000;
        private const uint PRUOFFSET_DRAM_PRU0 = 0x00000000;
        private const uint PRUOFFSET_DRAM_PRU1 = 0x00002000;
        private const uint PRUOFFSET_INTC_PRU0 = 0x00020000; // yes, PRU0 and PRU1 are the same offset
        private const uint PRUOFFSET_INTC_PRU1 = 0x00020000; //   they share an interrupt controller

        public const uint PRU_IRAM_SIZE = 0x2000;  // PRU has 8Kb of instruction RAM
        public const uint PRU_DRAM_SIZE = 0x2000;  // PRU has 8Kb of primary data RAM

        // some specific named offsets into the PRU INTC area
        private const uint PRUOFFSET_INTC_SIPR0 = 0xD00;
        private const uint PRUOFFSET_INTC_SIPR1 = 0xD04;

        private const uint PRUOFFSET_INTC_SITR0 = 0xD80;
        private const uint PRUOFFSET_INTC_SITR1 = 0xD84;

        private const uint PRUOFFSET_INTC_ESR0 = 0x300;
        private const uint PRUOFFSET_INTC_ESR1 = 0x304;

        private const uint PRUOFFSET_INTC_ECR0 = 0x380;
        private const uint PRUOFFSET_INTC_ECR1 = 0x384;

        private const uint PRUOFFSET_INTC_SECR0 = 0x280;
        private const uint PRUOFFSET_INTC_SECR1 = 0x284;

        private const uint PRUOFFSET_INTC_CMR00 = 0x400;
        private const uint PRUOFFSET_INTC_CMR01 = 0x404;
        private const uint PRUOFFSET_INTC_CMR02 = 0x408;
        private const uint PRUOFFSET_INTC_CMR03 = 0x40C;
        private const uint PRUOFFSET_INTC_CMR04 = 0x410;
        private const uint PRUOFFSET_INTC_CMR05 = 0x414;
        private const uint PRUOFFSET_INTC_CMR06 = 0x418;
        private const uint PRUOFFSET_INTC_CMR07 = 0x41C;
        private const uint PRUOFFSET_INTC_CMR08 = 0x420;
        private const uint PRUOFFSET_INTC_CMR09 = 0x424;
        private const uint PRUOFFSET_INTC_CMR10 = 0x428;
        private const uint PRUOFFSET_INTC_CMR11 = 0x42C;
        private const uint PRUOFFSET_INTC_CMR12 = 0x430;
        private const uint PRUOFFSET_INTC_CMR13 = 0x434;
        private const uint PRUOFFSET_INTC_CMR14 = 0x438;
        private const uint PRUOFFSET_INTC_CMR15 = 0x43C;

        private const uint PRUOFFSET_INTC_HMR0 = 0x800;
        private const uint PRUOFFSET_INTC_HMR1 = 0x804;
        private const uint PRUOFFSET_INTC_HMR2 = 0x808;

        private const uint PRUOFFSET_INTC_SICR = 0x024;
        private const uint PRUOFFSET_INTC_EISR = 0x028;
        private const uint PRUOFFSET_INTC_EICR = 0x02C;

        private const uint PRUOFFSET_INTC_HIEISR = 0x034;
        private const uint PRUOFFSET_INTC_HIDISR = 0x038;
        private const uint PRUOFFSET_INTC_GER = 0x010;
        private const uint PRUOFFSET_INTC_CR = 0x004;

        // there are only 10 HOST Interrupts, HostInt0,1 are for PRU to PRU
        // communications and HOSTInt2,9 are for interacting with C#
        // user space programs. The UIO subsystem interacts with these and
        // is hard coded so that HOSTINT2 appears on UIO0, HOSTINT3 on UIO1 etc.
        private const uint MIN_HOSTINT = 0;
        private const uint MAX_HOSTINT = 9;

        // there are only 10 interrupt Channels, We, in our standard
        // configuration, map these 1 to 1 onto the Host Interrupts
        // it does not have to be this way but it keeps things simple
        private const uint MIN_INTCHAN = 0;
        private const uint MAX_INTCHAN = 9;

        // there are 64 interrupts. Most of these are used by the 
        // PRU's peripherals (UART, Ethercat etc). SysInts 16-31
        // can be used to raise events in user space programs
        private const uint MIN_SYSINT = 0;
        private const uint MAX_SYSINT = 61;

        // these are the system interrupts the PRU itself can raise
        // by writing to bits R31[3:0] 
        private const uint MIN_PRUSYSINT = 16;
        private const uint MAX_PRUSYSINT = 31;

        // pointers into the pruMapped Memory
        private IntPtr pruMappedMem = IntPtr.Zero;

        // Track whether Dispose has been called. 
        private bool disposed = false;

        /// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="pruIDIn">The pru we interact with</param>
        /// <history>
        ///    05 Jun 15  Cynic - Originally written
        /// </history>
        public PRUDriver (PRUEnum pruIDIn)
        {
            // Console.WriteLine("Building PRUDriver for " + pruIDIn.ToString()); 
            // set some values
            pruID = pruIDIn;

            // set up the PRU
            SetupPRU();
        }
            
        /// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
        /// <summary>
        /// Finalizer
        /// </summary>
        /// <history>
        ///    05 Jun 15  Cynic - Originally written
        /// </history>
        ~PRUDriver()
        {
            Dispose(false);
        }
            
        /// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
        /// <summary>
        /// Loads a compiled binary into a PRU and runs it.
        /// </summary>
        /// <param name="binaryToRun">The binary file to run. Cannot be NULL</param>
        /// <history>
        ///    05 Jun 15  Cynic - Originally written
        /// </history>
        public void ExecutePRUProgram(string binaryToRun)
        {
            ExecutePRUProgram(binaryToRun, null);
        }

        /// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
        /// <summary>
        /// Loads a compiled binary into a PRU and runs it.
        /// </summary>
        /// <param name="binaryToRun">The filename and path to the 
        ///    binary file to run. Cannot be NULL</param>
        /// <param name="dataBytes">The databyes to load into the PRU dataRAM. The
        /// offset is assumed to be 0. This is intended for data which should be
        /// in place and which the PRU should find when it starts. Can be NULL</param>
        /// <history>
        ///    05 Jun 15  Cynic - Originally written
        /// </history>
        public void ExecutePRUProgram(string binaryToRun, byte[] dataBytes)
        {
            byte[] fileBytes = null;
            FileStream fs = null;

            if ((binaryToRun == null) || (binaryToRun.Length == 0))
            {
                throw new Exception("Null or zero length binary file name specified");
            }

            try
            {
                fs = new FileStream(binaryToRun, FileMode.Open,FileAccess.Read);
                if ((fs == null) || (fs.Length == 0))
                {
                    throw new Exception("The binary file stream: " + binaryToRun + " is null or zero bytes");
                }
                fileBytes = new byte[fs.Length];
                fs.Read(fileBytes,0,System.Convert.ToInt32(fs.Length));
            }
            finally
            {
                fs.Close();
                fs.Dispose();
            }         
            if ((fileBytes == null) || (fileBytes.Length == 0))
            {
                throw new Exception("The binary file: " + binaryToRun + " returned null or zero bytes on read");
            }
            if (fileBytes.Length > PRU_IRAM_SIZE)
            {
                throw new Exception("The binary file: " + binaryToRun + " contains more bytes (" + fileBytes.Length.ToString() + ") than the PRU instruction RAM can hold (" + PRU_IRAM_SIZE.ToString() + ")");
            }

            // Console.WriteLine("The binary file: " + binaryToRun + " has been read");

            // We disable the PRU first to reset it
            DisablePRU();

            // if we have databytes we load those
            if ((dataBytes != null) && (dataBytes.Length > 0))
            {
                // we load the data into the PRU local data memory with 0 offset
                WritePRUData(dataBytes, 0);
            }

            // we load the code into the PRU local instruction memory
            LoadPRUCode(ref fileBytes);

            // enable the PRU
            EnableAndExecutePRU(0);

        }

        /// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
        /// <summary>
        /// Stop the PRU.
        /// 
        /// <history>
        ///    05 Jun 15  Cynic - Originally written
        /// </history>
        public void PRUStop()
        {
            // get a pointer to the control register of the PRU as mapped by the UIO file
            IntPtr ctrlPtr = PRUUIOMappedControlBase;
            if (ctrlPtr == (IntPtr)(-1))
            {
                throw new IOException ("Failed to get pointer to PRU control register");
            }
            // this does it all
            DisablePRU();
        }

        /// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
        /// <summary>
        /// Loads the contents of a specified byte array into the PRU
        /// local instruction memory.
        /// 
        /// NOTE: the max size here is PRU_IRAM_SIZE
        /// </summary>
        /// <param name="codeBytes">The bytes of the compiled binary the 
        /// PRU is to run</param>
        /// <history>
        ///    05 Jun 15  Cynic - Originally written
        /// </history>
        private void LoadPRUCode(ref byte[] codeBytes)
        {

            // Console.WriteLine("LoadPRUCode called for " + PRUID.ToString());
            if ((codeBytes == null) || (codeBytes.Length == 0))
            {
                throw new Exception("The code bytes to load was null or zero length");
            }
            if (codeBytes.Length > PRU_IRAM_SIZE)
            {
                throw new Exception("The " + codeBytes.Length.ToString() + " code bytes to load are too long. Max is " + PRU_IRAM_SIZE.ToString());
            }
                
            // Console.WriteLine("LoadPRUCode IRAM Base is " + PRUUIOMappedIRAMBase.ToString("X8"));

            // get a pointer to the iram base register of the PRU as mapped by the UIO file
            IntPtr iramPtr = PRUUIOMappedIRAMBase;
            if (iramPtr == (IntPtr)(-1))
            {
                throw new IOException ("Failed to get pointer to PRU iram area");
            }
                
            // copy the data from the codeBytes to our instruction ram pointer               
            Marshal.Copy(codeBytes, 0, iramPtr, codeBytes.Length);
            // Console.WriteLine("LoadPRUCode complete for " + PRUID.ToString());
        }

        /// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
        /// <summary>
        /// Writes the contents of a specified byte into the PRU
        /// data memory.
        /// 
        /// NOTE: the max size here is PRU_DRAM_SIZE
        /// </summary>
        /// <param name="byteVal">The byte to write</param>
        /// <param name="offset">The offset into the PRU DataRAM we write the byte
        /// into. Note that the sizeof(byte) + offset cannot be more than the
        /// PRU_DRAM_SIZE</param>
        /// <history>
        ///    05 Jun 15  Cynic - Originally written
        /// </history>
        public void WritePRUDataByte(byte byteVal, uint offset)
        {
            byte[] dataBytes = new byte[sizeof(byte)];
            dataBytes[0] = byteVal;
            WritePRUData(dataBytes, offset);
        }

        /// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
        /// <summary>
        /// Writes the contents of a specified byte array into the PRU
        /// data memory.
        /// 
        /// </summary>
        /// <param name="byteArray">The byte array to write</param>
        /// <param name="offset">The offset into the PRU DataRAM we write the byte
        /// array into. Note that the byteArray.Length + offset cannot be more than the
        /// PRU_DRAM_SIZE</param>
        /// <history>
        ///    05 Jun 15  Cynic - Originally written
        /// </history>
        public void WritePRUDataByteArray(byte[] byteArray, uint offset)
        {
            WritePRUData(byteArray, offset);
        }

        /// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
        /// <summary>
        /// Writes the contents of a specified UInt16 into the PRU
        /// data memory.
        /// 
        /// NOTE: the max size here is PRU_DRAM_SIZE
        /// </summary>
        /// <param name="dataVal">The UInt16 to write</param>
        /// <param name="offset">The offset into the PRU DataRAM we write the UInt16
        /// into. Note that the sizeof(UInt16) + offset cannot be more than the
        /// PRU_DRAM_SIZE</param>
        /// <history>
        ///    05 Jun 15  Cynic - Originally written
        /// </history>
        public void WritePRUDataUInt16(UInt16 dataVal, uint offset)
        {
            byte[] dataBytes = new byte[sizeof(UInt16)];
            BitConverter.GetBytes(dataVal).CopyTo(dataBytes,0);
            WritePRUData(dataBytes, offset);           
        }

        /// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
        /// <summary>
        /// Writes the contents of a specified Int16 into the PRU
        /// data memory.
        /// 
        /// NOTE: the max size here is PRU_DRAM_SIZE
        /// </summary>
        /// <param name="dataVal">The Int16 to write</param>
        /// <param name="offset">The offset into the PRU DataRAM we write the Int16
        /// into. Note that the sizeof(Int16) + offset cannot be more than the
        /// PRU_DRAM_SIZE</param>
        /// <history>
        ///    05 Jun 15  Cynic - Originally written
        /// </history>
        public void WritePRUDataInt16(Int16 dataVal, uint offset)
        {
            byte[] dataBytes = new byte[sizeof(Int16)];
            BitConverter.GetBytes(dataVal).CopyTo(dataBytes,0);
            WritePRUData(dataBytes, offset);           
        }

        /// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
        /// <summary>
        /// Writes the contents of a specified UInt32 into the PRU
        /// data memory.
        /// 
        /// NOTE: the max size here is PRU_DRAM_SIZE
        /// </summary>
        /// <param name="dataVal">The UInt32 to write</param>
        /// <param name="offset">The offset into the PRU DataRAM we write the UInt32
        /// into. Note that the sizeof(uint) + offset cannot be more than the
        /// PRU_DRAM_SIZE</param>
        /// <history>
        ///    05 Jun 15  Cynic - Originally written
        /// </history>
        public void WritePRUDataUInt32(UInt32 dataVal, uint offset)
        {
            byte[] dataBytes = new byte[sizeof(UInt32)];
            BitConverter.GetBytes(dataVal).CopyTo(dataBytes,0);
            WritePRUData(dataBytes, offset);           
        }
            
        /// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
        /// <summary>
        /// Writes the contents of a specified Int32 into the PRU
        /// data memory.
        /// 
        /// NOTE: the max size here is PRU_DRAM_SIZE
        /// </summary>
        /// <param name="dataVal">The Int32 to write</param>
        /// <param name="offset">The offset into the PRU DataRAM we write the Int32
        /// into. Note that the sizeof(Int32) + offset cannot be more than the
        /// PRU_DRAM_SIZE</param>
        /// <history>
        ///    05 Jun 15  Cynic - Originally written
        /// </history>
        public void WritePRUDataInt32(Int32 dataVal, uint offset)
        {
            byte[] dataBytes = new byte[sizeof(Int32)];
            BitConverter.GetBytes(dataVal).CopyTo(dataBytes,0);
            WritePRUData(dataBytes, offset);           
        }

        /// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
        /// <summary>
        /// Writes the contents of a specified UInt64 into the PRU
        /// data memory.
        /// 
        /// NOTE: the max size here is PRU_DRAM_SIZE
        /// </summary>
        /// <param name="dataVal">The UInt64 to write</param>
        /// <param name="offset">The offset into the PRU DataRAM we write the UInt64
        /// into. Note that the sizeof(UInt64) + offset cannot be more than the
        /// PRU_DRAM_SIZE</param>
        /// <history>
        ///    05 Jun 15  Cynic - Originally written
        /// </history>
        public void WritePRUDataUInt64(UInt64 dataVal, uint offset)
        {
            byte[] dataBytes = new byte[sizeof(UInt64)];
            BitConverter.GetBytes(dataVal).CopyTo(dataBytes,0);
            WritePRUData(dataBytes, offset);           
        }

        /// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
        /// <summary>
        /// Writes the contents of a specified Int64 into the PRU
        /// data memory.
        /// 
        /// NOTE: the max size here is PRU_DRAM_SIZE
        /// </summary>
        /// <param name="dataVal">The Int64 to write</param>
        /// <param name="offset">The offset into the PRU DataRAM we write the Int64
        /// into. Note that the sizeof(Int64) + offset cannot be more than the
        /// PRU_DRAM_SIZE</param>
        /// <history>
        ///    05 Jun 15  Cynic - Originally written
        /// </history>
        public void WritePRUDataInt64(Int64 dataVal, uint offset)
        {
            byte[] dataBytes = new byte[sizeof(Int64)];
            BitConverter.GetBytes(dataVal).CopyTo(dataBytes,0);
            WritePRUData(dataBytes, offset);           
        }

        /// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
        /// <summary>
        /// 
        /// Reads a byte from the PRU data memory
        /// 
        /// </summary>
        /// <param name="offset">The offset into the PRU DataRAM we read the databytes
        /// from. Note that the sizeof(byte) + offset cannot be more than the
        /// PRU_DRAM_SIZE</param>
        /// <returns>
        ///   a byte read from the PRU Data Ram at the offset
        /// </returns>
        /// <history>
        ///    05 Jun 15  Cynic - Originally written
        /// </history>
        public byte ReadPRUDataByte(uint offset)
        {
            byte[] outBytes = ReadPRUData(offset, sizeof(byte));
            return outBytes[0];
        }

        /// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
        /// <summary>
        /// 
        /// Reads a byte array from the PRU data memory
        /// 
        /// </summary>
        /// <param name="offset">The offset into the PRU DataRAM we read the databytes
        /// from. Note that the length + offset cannot be more than the
        /// PRU_DRAM_SIZE</param>
        /// <param name="length">the number of bytes to read</param>
        /// <returns>
        ///   a byte array read from the PRU Data Ram at the offset
        /// </returns>
        /// <history>
        ///    05 Jun 15  Cynic - Originally written
        /// </history>
        public byte[] ReadPRUDataByteArray(uint offset, uint length)
        {
            return ReadPRUData(offset, length);
        }

        /// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
        /// <summary>
        /// Reads the PRU data memory and returns it as a UInt16
        /// 
        /// </summary>
        /// <param name="offset">The offset into the PRU DataRAM we read the databytes
        /// from. Note that the sizeof(UInt16) + offset cannot be more than the
        /// PRU_DRAM_SIZE</param>
        /// <returns>
        ///   a uint read from the PRU Data Ram at the offset
        /// </returns>
        /// <history>
        ///    05 Jun 15  Cynic - Originally written
        /// </history>
        public UInt16 ReadPRUDataUInt16(uint offset)
        {
            byte[] outBytes = ReadPRUData(offset, sizeof(UInt16));
            return BitConverter.ToUInt16(outBytes, 0);
        }

        /// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
        /// <summary>
        /// Reads the PRU data memory and returns it as a Int16
        /// 
        /// </summary>
        /// <param name="offset">The offset into the PRU DataRAM we read the databytes
        /// from. Note that the sizeof(Int16) + offset cannot be more than the
        /// PRU_DRAM_SIZE</param>
        /// <returns>
        ///   an Int16 read from the PRU Data Ram at the offset
        /// </returns>
        /// <history>
        ///    05 Jun 15  Cynic - Originally written
        /// </history>
        public Int16 ReadPRUDataInt16(uint offset)
        {
            byte[] outBytes = ReadPRUData(offset, sizeof(Int16));
            return BitConverter.ToInt16(outBytes, 0);
        }
            
        /// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
        /// <summary>
        /// Reads the PRU data memory and returns it as a UInt32
        /// 
        /// </summary>
        /// <param name="offset">The offset into the PRU DataRAM we read the databytes
        /// from. Note that the sizeof(UInt32) + offset cannot be more than the
        /// PRU_DRAM_SIZE</param>
        /// <returns>
        ///   a uint read from the PRU Data Ram at the offset
        /// </returns>
        /// <history>
        ///    05 Jun 15  Cynic - Originally written
        /// </history>
        public UInt32 ReadPRUDataUInt32(uint offset)
        {
            byte[] outBytes = ReadPRUData(offset, sizeof(UInt32));
            return BitConverter.ToUInt32(outBytes, 0);
        }

        /// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
        /// <summary>
        /// Reads the PRU data memory and returns it as a Int32
        /// 
        /// </summary>
        /// <param name="offset">The offset into the PRU DataRAM we read the databytes
        /// from. Note that the sizeof(Int32) + offset cannot be more than the
        /// PRU_DRAM_SIZE</param>
        /// <returns>
        ///   an Int32 read from the PRU Data Ram at the offset
        /// </returns>
        /// <history>
        ///    05 Jun 15  Cynic - Originally written
        /// </history>
        public Int32 ReadPRUDataInt32(uint offset)
        {
            byte[] outBytes = ReadPRUData(offset, sizeof(Int32));
            return BitConverter.ToInt32(outBytes, 0);
        }

        /// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
        /// <summary>
        /// Reads the PRU data memory and returns it as a UInt64
        /// 
        /// </summary>
        /// <param name="offset">The offset into the PRU DataRAM we read the databytes
        /// from. Note that the sizeof(UInt64) + offset cannot be more than the
        /// PRU_DRAM_SIZE</param>
        /// <returns>
        ///   a uint read from the PRU Data Ram at the offset
        /// </returns>
        /// <history>
        ///    05 Jun 15  Cynic - Originally written
        /// </history>
        public UInt64 ReadPRUDataUInt64(uint offset)
        {
            byte[] outBytes = ReadPRUData(offset, sizeof(UInt64));
            return BitConverter.ToUInt64(outBytes, 0);
        }

        /// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
        /// <summary>
        /// Reads the PRU data memory and returns it as a Int64
        /// 
        /// </summary>
        /// <param name="offset">The offset into the PRU DataRAM we read the databytes
        /// from. Note that the sizeof(Int64) + offset cannot be more than the
        /// PRU_DRAM_SIZE</param>
        /// <returns>
        ///   a int read from the PRU Data Ram at the offset
        /// </returns>
        /// <history>
        ///    05 Jun 15  Cynic - Originally written
        /// </history>
        public Int64 ReadPRUDataInt64(uint offset)
        {
            byte[] outBytes = ReadPRUData(offset, sizeof(Int64));
            return BitConverter.ToInt64(outBytes, 0);
        }

        /// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
        /// <summary>
        /// Reads the PRU data memory and returns it as a byte array
        /// 
        /// NOTE: the max size here is PRU_DRAM_SIZE
        /// </summary>
        /// <param name="offset">The offset into the PRU DataRAM we read the databytes
        /// from. Note that the length + offset cannot be more than the
        /// PRU_DRAM_SIZE</param>
        /// <param name="length">the number of bytes to read</param>
        /// <history>
        ///    05 Jun 15  Cynic - Originally written
        /// </history>
        private byte[] ReadPRUData(uint offset, uint length)
        {
            byte[] outBytes = null;

            //Console.WriteLine("ReadPRUData called for " + PRUID.ToString());
   
            // do we have an offset?
            if (offset > 0)
            {
                // yes, we do. Perform the test this way
                if ((length + offset) > PRU_DRAM_SIZE)
                {
                    throw new Exception("The " + length.ToString() + " length to read and offset of " + offset.ToString() + " are too long. Max is " + PRU_DRAM_SIZE.ToString());
                }
            }
            else
            {
                // no, we do not. Perform the test this way
                if (length > PRU_DRAM_SIZE)
                {
                    throw new Exception("The " + length.ToString() + " length to read is too long. Max is " + PRU_DRAM_SIZE.ToString());
                }
            }

            // create our output dataBytes array
            outBytes = new byte[length];

            // get a pointer to the data register of the PRU as mapped by the UIO file
            // this assumes an offset of 0
            IntPtr dramPtr = PRUUIOMappedDRAMBase;
            if (dramPtr == (IntPtr)(-1))
            {
                throw new IOException ("Failed to get pointer to PRU DRAM area");
            }
            // do we need to adjust for a user specified offset?
            if (offset != 0)
            {
                dramPtr = new IntPtr(dramPtr.ToInt64() + (long)offset);
            }

            // copy the data from our data ram pointer to the output array            
            Marshal.Copy(dramPtr, outBytes, 0, (Int32)length);
            return outBytes;
        }

        /// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
        /// <summary>
        /// Loads the contents of a specified byte array into the PRU
        /// data memory.
        /// 
        /// NOTE: the max size here is PRU_DRAM_SIZE
        /// </summary>
        /// <param name="dataBytes">The bytes of the data to load</param>
        /// <param name="offset">The offset into the PRU DataRAM we load the databytes
        /// into. Note that the databytes.Length + offset cannot be more than the
        /// PRU_DRAM_SIZE</param>
        /// <history>
        ///    05 Jun 15  Cynic - Originally written
        /// </history>
        private void WritePRUData(byte[] dataBytes, uint offset)
        {
            //Console.WriteLine("WritePRUData called for " + PRUID.ToString());
            if ((dataBytes == null) || (dataBytes.Length == 0))
            {
                throw new Exception("The data bytes to load was null or zero length");
            }

            // do we have an offset?
            if (offset > 0)
            {
                // yes, we do. Perform the test this way
                if ((dataBytes.Length + offset) > PRU_DRAM_SIZE)
                {
                    throw new Exception("The " + dataBytes.Length.ToString() + " data bytes to load and offset of " + offset.ToString() + " are too long. Max is " + PRU_DRAM_SIZE.ToString());
                }
            }
            else
            {
                // no, we do not. Perform the test this way
                if (dataBytes.Length > PRU_DRAM_SIZE)
                {
                    throw new Exception("The " + dataBytes.Length.ToString() + " data bytes to load are too long. Max is " + PRU_DRAM_SIZE.ToString());
                }
            }

            // Console.WriteLine("WritePRUData DRAM Base is " + PRUUIOMappedDRAMBase.ToString("X8"));

            // get a pointer to the data register of the PRU as mapped by the UIO file
            // this assumes an offset of 0
            IntPtr dramPtr = PRUUIOMappedDRAMBase;
            if (dramPtr == (IntPtr)(-1))
            {
                throw new IOException ("Failed to get pointer to PRU DRAM area");
            }
            // do we need to adjust for a user specified offset?
            if (offset != 0)
            {
                dramPtr = new IntPtr(dramPtr.ToInt64() + (long)offset);
            }

            // copy the data from the dataBytes to our data ram pointer               
            Marshal.Copy(dataBytes, 0, dramPtr, dataBytes.Length);
            // Console.WriteLine("WritePRUData complete for " + PRUID.ToString());
        }

        /// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
        /// <summary>
        /// Disables and resets the PRU
        /// </summary>
        /// <history>
        ///    05 Jun 15  Cynic - Originally written
        /// </history>
        private void DisablePRU()
        {

            // Console.WriteLine("DisablePRU called for " + PRUID.ToString());
            // Console.WriteLine("DisablePRU Control Offset is " + PRUUIOMappedControlBase.ToString("X8"));

            // get a pointer to the control register of the PRU as mapped by the UIO file
            IntPtr ctrlPtr = PRUUIOMappedControlBase;
            if (ctrlPtr == (IntPtr)(-1))
            {
                throw new IOException ("Failed to get pointer to PRU control register");
            }
            // explicitly build the 32 bit data we write in order to disable and reset
            // the PRU. This is being done in a long winded manner so you can see 
            // exactly what is being done and why. Really we could just set it 
            // to 0x00000001 and be done with it
            uint ctrlRegisterValue = 0;
            // clear the two high bytes these are the starting address
            ctrlRegisterValue = ctrlRegisterValue & 0x0000FFFF;
            // clear bit 8 this turns off SINGLE_STEP this also turns off 
            // bits 15-9 which are read-only or undefined
            ctrlRegisterValue = ctrlRegisterValue & 0xFFFF00FF;
            // clear bits 7-1, a 0 in bit 3 turns off COUNTER_ENABLE
            // a 0 in bit 2 turns off SLEEPING
            // a 0 in bit 1 turns off ENABLE (ie the PRU is disabled)
            ctrlRegisterValue = ctrlRegisterValue & 0xFFFF0000;
            // a 1 in bit 0 forces a SOFT_RESET when this bit is set
            //     back to 0 in a later EnablePRU() call
            ctrlRegisterValue = ctrlRegisterValue | 0x00000001;
            // Console.WriteLine("DisablePRU Control ctrlRegisterValue is " + ctrlRegisterValue.ToString("X8"));

            // write the data to the control register
            Marshal.WriteInt32(ctrlPtr, (int)ctrlRegisterValue);
            // Console.WriteLine("DisablePRU complete for " + PRUID.ToString());
        }

        /// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
        /// <summary>
        /// Enables the PRU, and runs the binary code loaded into its instruction
        /// memory.
        /// 
        /// NOTE: the PRU has to start its code on a 32 bit boundary. This means
        /// that the starting address in bytes will be divided by four before
        /// being set in the Control register. Your actual start point in the 
        /// binary file should be at a 32 bit address.
        /// 
        /// </summary>
        /// <param name="startOffsetInBytes">The starting address (in bytes) at
        /// which the PRU should begin executing.</param>
        /// code in the 
        /// <history>
        ///    05 Jun 15  Cynic - Originally written
        /// </history>
        private void EnableAndExecutePRU(uint startOffsetInBytes)
        {

            // Console.WriteLine("EnablePRU called for " + PRUID.ToString());
            // Console.WriteLine("EnablePRU Controll Offset is " + PRUUIOMappedControlBase.ToString("X8"));

            // get a pointer to the control register of the PRU as mapped by the UIO file
            IntPtr ctrlPtr = PRUUIOMappedControlBase;
            if (ctrlPtr == (IntPtr)(-1))
            {
                throw new IOException ("Failed to get pointer to PRU control register");
            }

            // explicitly build the 32 bit data we write in order to enable and
            // run the code in the PRU. This is being done in a long winded manner 
            // so you can see  exactly what is being done and why Really we could 
            // just divide the incoming offset by 4, place it in the top two bytes
            // and set the low word to 0x02 and be done with it
            uint ctrlRegisterValue = 0;
            // divide the incoming byte offset by 4 so it is an instruction start
            // shift it up in into the top two bytes and set it as the starting address
            ctrlRegisterValue = ctrlRegisterValue | ((startOffsetInBytes/4) << 16);
            // clear bit 8 this turns off SINGLE_STEP this also turns off 
            // bits 15-9 which are read-only or undefined
            ctrlRegisterValue = ctrlRegisterValue & 0xFFFF00FF;
            // clear bits 7-1, a 0 in bit 3 turns off COUNTER_ENABLE
            // a 0 in bit 2 turns off SLEEPING
            ctrlRegisterValue = ctrlRegisterValue & 0xFFFF0000;
            // a 1 in bit 1 turns on ENABLE (ie the PRU is enabled)
            ctrlRegisterValue = ctrlRegisterValue | 0x00000002;
            // a 0 in bit 0 forces a SOFT_RESET if this bit was set
            //     to 1 in a earlier DisablePRU() call
            ctrlRegisterValue = ctrlRegisterValue & 0xFFFFFFF6;
            // Console.WriteLine("EnablePRU Controll ctrlRegisterValue is " + ctrlRegisterValue.ToString("X8"));

            // write the data
            Marshal.WriteInt32(ctrlPtr, (int)ctrlRegisterValue);
            // Console.WriteLine("EnablePRU complete for " + PRUID.ToString());
        }
            
        /// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
        /// <summary>
        /// Sets up the pru. Must be done prior to loading and executing a binary
        /// </summary>
        /// <history>
        ///    05 Jun 15  Cynic - Originally written
        /// </history>
        private void SetupPRU()
        {
            int pruMMap_fd = -1;

            try
            {
                // Now we want to get the memory map for the PRU. To do this we need a UIO device
                // Since we close the device after we set up the memory map it doesn't really
                // matter which one we use. We alway just use PRUUIODeviceEnum.PRUUIODEVICE_0

                // get the filename for the pru uio device. If this does not exist then 
                // the modprobe(uio_pruss) has not been issued or the BB-BONE-PRU-01 overlay
                // has not been setup in the device tree
                // We expect to see that the files /dev/uio[0-7] exist
                string pruUIOFile = PRU_UIODEVICE_NAME.Replace(PRUUIONUM,GetPruUIODeviceAsNumber(PRUUIODeviceEnum.PRUUIODEVICE_0).ToString());
                // Console.WriteLine("pruUIOFile=" +pruUIOFile); 

                // open the pru uio device it is a device file that maps the pru address space 
                // into user space memory. This file is also used to send and receive interrupt
                // events to and from the PRU. However, this function does not care about that.
                pruMMap_fd = Syscall.open(pruUIOFile, OpenFlags.O_RDWR|OpenFlags.O_SYNC);
                if (pruMMap_fd == -1) throw new Exception ("Could not open UIO device file: " + pruUIOFile);

                /* NOT NECESSARY? the prussdrv.c code does this but I do not know why
                 * it does not appear to be used.
                // read the line from the UIO base file. This will tell us the base address of
                // the pru subsystem, we can use it to figure out specific values for PRU0 and PRU1
                string pruUIOBaseFile = PRU_UIO_BASE_PATH.Replace(PRUUIONUM,GetPruUIODeviceAsNumber().ToString());
                string[] baseLines = System.IO.File.ReadAllLines(pruUIOBaseFile);
                if ((baseLines == null) || (baseLines.Length == 0)) throw new Exception("Error opening and reading pruUIOBaseFile: " + pruUIOBaseFile);
                // convert to a UINT
                pruPhysBaseAddr = Convert.ToUInt32(baseLines[0], 16);
      
                // read the line from the UIO size file. This will tell us the size of the PRU
                // subsystem mapped into user space
                string pruUIOSizeFile = PRU_UIO_SIZE_PATH.Replace(PRUUIONUM,GetPruUIODeviceAsNumber().ToString());
                string[] sizeLines = System.IO.File.ReadAllLines(pruUIOSizeFile);
                if ((sizeLines == null) || (sizeLines.Length == 0)) throw new Exception("Error opening and reading pruUIOSizeFile: " + pruUIOSizeFile);
                // convert to a UINT
                pruPhysMapSize = Convert.ToUInt32(sizeLines[0], 16);
*/
                pruPhysMapSize = PRU_UIO_PHYSMAPSIZE;
                // Console.WriteLine("pruPhysBaseAddr " + pruPhysBaseAddr.ToString("X8")); 
                // Console.WriteLine("pruPhysMapSize " + pruPhysMapSize.ToString("X8")); 

                // map in the PRU Device File into memory 
                pruMappedMem = Syscall.mmap (IntPtr.Zero, pruPhysMapSize, MmapProts.PROT_WRITE | MmapProts.PROT_READ, MmapFlags.MAP_SHARED, pruMMap_fd, PRU_UIO_MMAP_BASE);
                if (pruMappedMem == (IntPtr)(-1))
                {
                    throw new IOException ("mmap failed for pru device file " + "(" + pruPhysBaseAddr.ToString("X8") + ", " + pruPhysMapSize.ToString("X8") + ")");
                }
                // Console.WriteLine("PRU Memory Now Mapped pruMappedMem="+pruMappedMem.ToString("X8")); 

            }
            finally
            {
                // once the memory is mapped the file can be closed.
                if (pruMMap_fd > 0)
                {
                    Syscall.close(pruMMap_fd);
                    pruMMap_fd = -1;
                }
            }
        }
            
        /// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
        /// <summary>
        /// Resets and initializes the interrupts and events for all PRU's (PRU0 and PRU1).
        /// 
        /// The PRU's share a interrupt controller subsystem so calling this 
        /// function once is sufficient for both PRUs.
        /// 
        /// Note: if you set up events for one PRU then call this to init
        ///       the events for the other PRU you will clear the event
        ///       configuration for the first PRU. Call this function 
        ///       once!
        /// <history>
        ///    05 Jun 15  Cynic - Originally written
        /// </history>
        public void InitInterruptsForAllPRUs()
        {
            // reset everything, get it all back to square 1
            ResetInterruptsForAllPRUs();
            // set up the standard mapping from which the user can
            // enable or disable specific interrupts
            SetupStandardHostAndChannelMappingForAllPRUs();
        }

        /// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
        /// <summary>
        /// The system interrupts, channels, hosts and corresponding events
        /// and interrupts can be configured in a near infinite number of ways.
        /// 
        /// In order to keep things simple a standard, documented, arrangement
        /// is configured and the user can then enable specific interrupts to
        /// perform the desired actions. This does mean that many of the PRU
        /// system interrupts are left unconnected. This setup is intended
        /// primarily to let the PRU trigger a host interrupt which will propagate
        /// down to user space and be visible on one of the uio* files
        /// 
        /// The PRU's share a interrupt controller subsystem so calling this 
        /// function sets the standard configuration for both PRUs
        /// 
        /// NOTE: a call to ResetInterruptsForAllPRUs() should be made to reset
        /// things before calling this function
        /// 
        /// <history>
        ///    05 Jun 15  Cynic - Originally written
        /// </history>
        public void SetupStandardHostAndChannelMappingForAllPRUs()
        {
            byte[] fourDataBytes = new byte[sizeof(UInt32)];

            if (pruMappedMem == (IntPtr)(-1))
            {
                throw new Exception ("Could not map standard PRU events, device file failed to open.");
            }  

            // get a pointer to the interrupt controller of the PRU as mapped by the UIO file
            IntPtr intcPtr = PRUUIOMappedINTCBase;
            if (intcPtr == (IntPtr)(-1))
            {
                throw new IOException ("Failed to get pointer to PRU INTC area");
            }

            // ########
            // ######## CMR[00-15]: Setup the mapping of the system interrupts to the channels
            // ########

            // The 16 Channel Map Registers specify the channel for the system interrupts 0 to 63. 
            // There is one register per 4 system interrupts. We are only interested in SysInts 16-31
            // here since these are the only ones the PRU can raise. The others are all raised by
            // various peripherals such as the UART or EtherCat. All interrupts are mapped onto
            // channel 0 (they have to go somewhere) and SysInts 16-23 are mapped onto Channels 2-9
            // SysInts  24-29 onto Channel 9, SysInt 30 onto Channel 0 and SysInt 31 onto Channel 1

            // our standard mapping is...
            //   systemInterrupt0 -> channel0
            //   systemInterrupt1 -> channel0
            //   ...
            //   systemInterrupt15-> channel0
            //   systemInterrupt16->  channel2
            //   systemInterrupt17->  channel3
            //   ...
            //   systemInterrupt23 -> channel9
            //   systemInterrupt24->  channel9
            //   systemInterrupt25->  channel9
            //   ...
            //   systemInterrupt29 -> channel9
            //   systemInterrupt30->  channel0
            //   systemInterrupt31->  channel1
            //   systemInterrupt32 -> channel0
            //   ...
            //   systemInterrupt62 -> channel0
            //   systemInterrupt63 -> channel0

            // these CMRxx registers work by setting the channel number for the system interrupt
            // in the bottom four bits of each byte. For example CMR00 maps sysInts0,1,2,3
            // so if we write a byte like 0x00030501 to CMR00 it means that system interrupt
            // 0 is mapped to channel 1, sysInt 2 to channel 5, sysint 3 to channel 3 and 
            // sysInt 4 is mapped to channel 0

            // map sysInts 0-15 to channel 0
            UInt32 cmrSetVal = 0x00000000;
            BitConverter.GetBytes(cmrSetVal).CopyTo(fourDataBytes,0);
            IntPtr cmrPtr = new IntPtr(intcPtr.ToInt64() + (long)PRUOFFSET_INTC_CMR00);     
            Marshal.Copy(fourDataBytes, 0, cmrPtr, fourDataBytes.Length);
            BitConverter.GetBytes(cmrSetVal).CopyTo(fourDataBytes,0);
            cmrPtr = new IntPtr(intcPtr.ToInt64() + (long)PRUOFFSET_INTC_CMR01);     
            Marshal.Copy(fourDataBytes, 0, cmrPtr, fourDataBytes.Length);
            BitConverter.GetBytes(cmrSetVal).CopyTo(fourDataBytes,0);
            cmrPtr = new IntPtr(intcPtr.ToInt64() + (long)PRUOFFSET_INTC_CMR02);     
            Marshal.Copy(fourDataBytes, 0, cmrPtr, fourDataBytes.Length);
            BitConverter.GetBytes(cmrSetVal).CopyTo(fourDataBytes,0);
            BitConverter.GetBytes(cmrSetVal).CopyTo(fourDataBytes,0);
            cmrPtr = new IntPtr(intcPtr.ToInt64() + (long)PRUOFFSET_INTC_CMR03);     
            Marshal.Copy(fourDataBytes, 0, cmrPtr, fourDataBytes.Length);
            // map sysInts 16-19 to channels 2-5
            cmrSetVal = 0x05040302;
            BitConverter.GetBytes(cmrSetVal).CopyTo(fourDataBytes,0);
            cmrPtr = new IntPtr(intcPtr.ToInt64() + (long)PRUOFFSET_INTC_CMR04);     
            Marshal.Copy(fourDataBytes, 0, cmrPtr, fourDataBytes.Length);
            // map sysInts 20-23 to channels 6-9
            cmrSetVal = 0x09080706;
            BitConverter.GetBytes(cmrSetVal).CopyTo(fourDataBytes,0);
            cmrPtr = new IntPtr(intcPtr.ToInt64() + (long)PRUOFFSET_INTC_CMR05);     
            Marshal.Copy(fourDataBytes, 0, cmrPtr, fourDataBytes.Length);
            // map sysInts 24-27 to channel 9
            cmrSetVal = 0x09090909;
            BitConverter.GetBytes(cmrSetVal).CopyTo(fourDataBytes,0);
            cmrPtr = new IntPtr(intcPtr.ToInt64() + (long)PRUOFFSET_INTC_CMR06);     
            Marshal.Copy(fourDataBytes, 0, cmrPtr, fourDataBytes.Length);
            // map sysInts 28-29 to channel 9 and sysInts 30-31 to channels 0 and 1
            cmrSetVal = 0x01000909;
            BitConverter.GetBytes(cmrSetVal).CopyTo(fourDataBytes,0);
            cmrPtr = new IntPtr(intcPtr.ToInt64() + (long)PRUOFFSET_INTC_CMR07);     
            Marshal.Copy(fourDataBytes, 0, cmrPtr, fourDataBytes.Length);
            // map sysInts 32-61 to channel 0
            cmrSetVal = 0x00000000;
            BitConverter.GetBytes(cmrSetVal).CopyTo(fourDataBytes,0);
            cmrPtr = new IntPtr(intcPtr.ToInt64() + (long)PRUOFFSET_INTC_CMR08);     
            Marshal.Copy(fourDataBytes, 0, cmrPtr, fourDataBytes.Length);
            cmrPtr = new IntPtr(intcPtr.ToInt64() + (long)PRUOFFSET_INTC_CMR09);     
            Marshal.Copy(fourDataBytes, 0, cmrPtr, fourDataBytes.Length);
            cmrPtr = new IntPtr(intcPtr.ToInt64() + (long)PRUOFFSET_INTC_CMR10);     
            Marshal.Copy(fourDataBytes, 0, cmrPtr, fourDataBytes.Length);
            cmrPtr = new IntPtr(intcPtr.ToInt64() + (long)PRUOFFSET_INTC_CMR11);     
            Marshal.Copy(fourDataBytes, 0, cmrPtr, fourDataBytes.Length);
            cmrPtr = new IntPtr(intcPtr.ToInt64() + (long)PRUOFFSET_INTC_CMR12);     
            Marshal.Copy(fourDataBytes, 0, cmrPtr, fourDataBytes.Length);
            cmrPtr = new IntPtr(intcPtr.ToInt64() + (long)PRUOFFSET_INTC_CMR13);     
            Marshal.Copy(fourDataBytes, 0, cmrPtr, fourDataBytes.Length);
            cmrPtr = new IntPtr(intcPtr.ToInt64() + (long)PRUOFFSET_INTC_CMR14);     
            Marshal.Copy(fourDataBytes, 0, cmrPtr, fourDataBytes.Length);
            cmrPtr = new IntPtr(intcPtr.ToInt64() + (long)PRUOFFSET_INTC_CMR15);     
            Marshal.Copy(fourDataBytes, 0, cmrPtr, fourDataBytes.Length);

            // ########
            // ######## HMR[0-2]: set the mapping of the channels to the Host Interrupts
            // ########

            // The Host Interrupt Map Registers define the host interrupts for channels. 
            // There is one register per 4 channels. We simply map channel0 to host 0
            // channel 1 to host 1 and so on

            // map channel 0-3 to hosts 0-3
            UInt32 hmrSetVal = 0x03020100;
            BitConverter.GetBytes(hmrSetVal).CopyTo(fourDataBytes,0);
            IntPtr hmrPtr = new IntPtr(intcPtr.ToInt64() + (long)PRUOFFSET_INTC_HMR0);     
            Marshal.Copy(fourDataBytes, 0, hmrPtr, fourDataBytes.Length);
            // map channel 4-7 to hosts 4-7
            hmrSetVal = 0x07060504;
            BitConverter.GetBytes(hmrSetVal).CopyTo(fourDataBytes,0);
            hmrPtr = new IntPtr(intcPtr.ToInt64() + (long)PRUOFFSET_INTC_HMR1);     
            Marshal.Copy(fourDataBytes, 0, hmrPtr, fourDataBytes.Length);
            // map channel 8-9 to hosts 8-9
            hmrSetVal = 0x00000908;
            BitConverter.GetBytes(hmrSetVal).CopyTo(fourDataBytes,0);
            hmrPtr = new IntPtr(intcPtr.ToInt64() + (long)PRUOFFSET_INTC_HMR2);     
            Marshal.Copy(fourDataBytes, 0, hmrPtr, fourDataBytes.Length);

            // ########
            // ######## ESR[0-1]: Enable system interrupts mapping to channels
            // ########

            // The System Interrupt Enable Set Registers enable system interrupts to trigger outputs. System
            // interrupts that are not enabled do not interrupt the host. There is a bit per system interrupt.

            // set up a suitable data structure
            UInt32 esrResetVal = 0x00ff0000; // ie  0000 0000 1111 1111 0000 0000 0000 0000
            BitConverter.GetBytes(esrResetVal).CopyTo(fourDataBytes,0);
            // get a pointer to ECR0, and copy the data
            IntPtr esrPtr = new IntPtr(intcPtr.ToInt64() + (long)PRUOFFSET_INTC_ESR0);     
            Marshal.Copy(fourDataBytes, 0, esrPtr, fourDataBytes.Length);
            esrResetVal = 0x00000000;   // ie enable nothing
            BitConverter.GetBytes(esrResetVal).CopyTo(fourDataBytes,0);
            // get a pointer to ECR1, and copy the data
            esrPtr = new IntPtr(intcPtr.ToInt64() + (long)PRUOFFSET_INTC_ESR1);     
            Marshal.Copy(fourDataBytes, 0, esrPtr, fourDataBytes.Length);

            // at this point we have setup the standard mapping. So activating
            // systemInterrupt16 will propagate through channel2 to host interrupt2
            // similarly systemInterrupt17 will propagate through channel3 to host interrupt3

            // hostInterrupts0 and 1 are left by the UIO subsystem for signalling between
            // the two PRUs because the functionality to support this is hard coded into 
            // the PRU INTC system. Host Interrupts 2 to 9 are hard coded by the uio subsystem
            // to appear in userspace via the uio device files /dev/uio0 to /dev/uio7. 

            // This means that if you trigger PRU systemInterrupt16 you will see it appear
            // on /dev/uio0. Similarly, triggering PRU systemInterrupt17 will cause it to
            // appear on /dev/uio1 The UIO file number is always TWO less than the 
            // system interrupt. NOTE: the only reason this works like that is because the 
            // above standard setup routes sysInt2 through channel2 through host2 and
            // host2 is tied to uio0 by the UIO subsystem. 

            // If the above mapping seems complicated to you that is because
            // it is designed to be easy to conceptualize on the input and output. Basically
            // writing a 0 (sysint16) to R31 bits 3-0 makes the interrupt happen on uio0 in user space.
            // Similarly writing a 1 (sysint17) makes the event happen on uio1. The two PRU-to-PRU 
            // interrupts (sysint30 and sysint31) are a bit different. Writing a 14 (sysint30) 
            // makes the PRU activate the interrupt flag on R31 bit 30 and writing a 15 (sysInt31)
            // activates the interrupt flag on R31 bit 31. Thus the input and output are 
            // roughly correlated in a hopefully memorable way.

            // another way to think if it is that things have been arranged so that the 
            // bottom 4 bits of the System Interrupt represent the uio file number on
            // which the event appears and also are equal to the PRUEventEnum number
            // Similarly, if you write a value of 14 (bottom 4 bits of 30) to R31 you trigger 
            // sysInt30 which appears on R31 bit 30 when it is read and so on.

            // NOTE that only the mapping has happened here. None of the system interrupts
            // or host interrupts or the global interrupts are enabled yet. That is done
            // by the userspace program via the EnablePRUEvent() call


        }
                   
        /// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
        /// <summary>
        /// Enables a PRU System Event at the Host level. Our standard mapping has ensured 
        /// that PRU systemInterrupts 16-23 are mapped to host Interrupts 2-9 and all
        /// we need to do is enable that if we want to use the functionality.
        /// 
        /// NOTE: hostInterrupts0 and 1 are left by the UIO subsystem for signalling between
        /// the two PRUs because the functionality to support this is hard coded into 
        /// the PRU INTC system. Host Interrupts 2 to 9 appear in userspace via the 
        /// uio device files /dev/uio0 to /dev/uio7. 
        ///
        /// This means that if you trigger PRU systemInterrupt16 you will see it appear
        /// on /dev/uio0. Similarly triggering PRU systemInterrupt17 will cause it to
        /// appear on /dev/uio1 The UIO file number which receives the event 
        /// is always the low four bits of the system interrupt. The only reason this 
        /// works like that is because the standard setup routes sysInt16 through channel2 
        /// through host2 and host2 is tied to uio0 by the UIO subsystem.
        ///
        /// </summary>
        /// <param name="pruEventIn">the host event.
        /// This must have been opened with a call to OpenPRUEvent</param>
        /// <history>
        ///    05 Jun 15  Cynic - Originally written
        /// </history>
        public void EnablePRUHostEvent(PRUEventEnum pruEventIn)
        {
            byte[] fourDataBytes = new byte[sizeof(UInt32)];

            if (pruMappedMem == (IntPtr)(-1))
            {
                throw new Exception ("Could not enable PRU event " + pruEventIn.ToString() + " device file failed to open.");
            }

            // open up the event uio file
            OpenPRUHostEvent(pruEventIn);

            // check we have a key
            if (enabledEvents.ContainsKey(pruEventIn) == false)
            {
                throw new Exception ("Could not enable PRU event " + pruEventIn.ToString() + " host event is not present");
            }  
            if ((enabledEvents[pruEventIn] == null) || (enabledEvents[pruEventIn].EventFD == PRUEventHandler.NO_FD))
            {
                // should never happen
                throw new Exception ("Could not enable PRU event " + pruEventIn.ToString() + " host event is not open");
            }  

            // get a pointer to the interrupt controller of the PRU as mapped by the UIO file
            IntPtr intcPtr = PRUUIOMappedINTCBase;
            if (intcPtr == (IntPtr)(-1))
            {
                throw new IOException ("Failed to get pointer to PRU INTC area");
            }

            // ########
            // ######## SECR0: Be sure to remove and clear any interrupts which may be pending
            // ########

            // The System Interrupt Status Enabled/Clear Registers show the pending 
            // enabled status of the system interrupts. We write a 1 to the register
            // bit which represents the system interrupt. 

            // Note our standard configuration only uses System interrupts 16-31 so we only
            // need to deal with SECR0

            // put a 1 in position 0
            UInt32 secrResetVal = 0x00000001;
            // shift it up into position according to the system interrupt number, the
            // host event to system event correlation is entirely dependent on the 
            // standard configuration we use
            secrResetVal = secrResetVal << GetSystemEventNumberFromPruEvent(pruEventIn);
            BitConverter.GetBytes(secrResetVal).CopyTo(fourDataBytes,0);

            // get a pointer to SECR0, and copy the data
            IntPtr secrPtr = new IntPtr(intcPtr.ToInt64() + (long)PRUOFFSET_INTC_SECR0);     
            Marshal.Copy(fourDataBytes, 0, secrPtr, fourDataBytes.Length);
            // we can ignore SECR1, since we are ignoring any system interrupts it configures

            // ########
            // ######## HIEISR: Enable the triggering of the host interrupts
            // ########

            // these are mapped onto the UIO files as part of the standard setup
            // HostEvents 0,1 are reserved for PRU to PRU interrupts. Host Event 2 
            // goes to UIO0, Host Event 1 to UIO1 etc

            // The Host Interrupt Enable Indexed Set Register enables a host interrupt output. 
            // The host 0-9 interrupt to enable is the value written. Only the bottom 10 bits are used

            // get the host interrupt which corresponts to the pruEvent
            UInt32 hieisrSetVal = GetHostEventFromPruEvent(pruEventIn);
            BitConverter.GetBytes(hieisrSetVal).CopyTo(fourDataBytes,0);

            // get a pointer to HIEISR, and copy the data
            IntPtr hieisrPtr = new IntPtr(intcPtr.ToInt64() + (long)PRUOFFSET_INTC_HIEISR);     
            Marshal.Copy(fourDataBytes, 0, hieisrPtr, fourDataBytes.Length);

            // ########
            // ######## GER: Enable all interrupts - host or PRU system
            // ########

            // The Global Host Interrupt Enable Register enables all the host interrupts. 
            // Individual host interrupts are still enabled or disabled from their individual 
            // enables and are not overridden by the global enable. 

            // this is just set to 0x01 to enable and 0x00 to disable 

            // set up a suitable data structure
            UInt32 gerResetVal = 0x00000001;
            BitConverter.GetBytes(gerResetVal).CopyTo(fourDataBytes,0);

            // get a pointer to GER, and copy the data
            IntPtr gerPtr = new IntPtr(intcPtr.ToInt64() + (long)PRUOFFSET_INTC_GER);     
            Marshal.Copy(fourDataBytes, 0, gerPtr, fourDataBytes.Length);

        }

        /// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
        /// <summary>
        /// Opens up a PRU Host Event. These correspond one-to-one to a uio file
        /// </summary>
        /// <param name="pruEventIn">the host event we open</param>
        /// <history>
        ///    05 Jun 15  Cynic - Originally written
        /// </history>
        private void OpenPRUHostEvent(PRUEventEnum pruEventIn)
        {

            if (pruMappedMem == (IntPtr)(-1))
            {
                throw new Exception ("Could not open PRU event " + pruEventIn.ToString() + " device file failed to open.");
            }

            // check the event record
            if (enabledEvents.ContainsKey(pruEventIn) == true)
            {
                // we have an event, but is it open?
                if (enabledEvents[pruEventIn].EventFD != PRUEventHandler.NO_FD)
                {
                    throw new Exception("Could not open PRU event " + pruEventIn.ToString() + " event is already open");
                }
            }
            else
            {
                // add the event record now
                enabledEvents.Add(pruEventIn, new PRUEventHandler(pruEventIn, this));
            }

            // the events (interrupts) are sent and received by interacting
            // the apropriate UIO device - we must open this device.
            // the later configuration of the event will enable the 
            // pre-configured system interrupts to appear on this device file

            // convert the event to a device
            PRUUIODeviceEnum eventUIODevice = GetPruUIODeviceFromPRUEvent(pruEventIn);

            try
            {
                // get the filename for the pru uio device. 
                string pruUIOFile = PRU_UIODEVICE_NAME.Replace(PRUUIONUM,GetPruUIODeviceAsNumber(eventUIODevice).ToString());
 
                // open the pru uio device. Besides the fact that it is a device file that maps 
                // the pru address space into user space memory it also can send and receive 
                // events from the PRU by being written to and read from in a certain way. Because
                // we wish to have the capability of enabling multiple events and the event
                // number is tied to the file, we do not use the memory map uio file for the 
                // uio event file - even though we could if we only had one event. It is better
                // to just open another copy and use that.
                enabledEvents[pruEventIn].EventFD = Syscall.open(pruUIOFile, OpenFlags.O_RDWR|OpenFlags.O_SYNC);
                if (enabledEvents[pruEventIn].EventFD == PRUEventHandler.NO_FD) throw new Exception ("Could not open UIO device event file: " + pruUIOFile);

                // Console.WriteLine("PRU Event opened "+pruEventIn.ToString()); 
                // Console.WriteLine("PRU Event pruUIOFile  "+pruUIOFile); 
            }
            finally
            {
            }
        }
            
        /// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
        /// <summary>
        /// Waits for the specified event. Will stall with a blocking read
        /// until the event is triggered. It also possible to set up an
        /// asynchronous event via the PRUEventMonitorOn/Off call and
        /// receive the events via a handler function while the main
        /// thread is doing something else
        /// </summary>
        /// <param name="pruEventIn">the host event we wait for</param>
        /// <returns>
        ///   the total number of events processed by the uio file (from any
        ///   source) since boot up.
        /// </returns>
        /// <history>
        ///    05 Jun 15  Cynic - Originally written
        /// </history>
        public UInt32 WaitForEvent(PRUEventEnum pruEventIn)
        {
            if (enabledEvents[pruEventIn].EventFD == PRUEventHandler.NO_FD)
            {
                throw new Exception("Host Event Not Open");
            }
                
            // call the wait function in the PRUEventHandler
            return enabledEvents[pruEventIn].WaitForEvent();
        }

        /// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
        /// <summary>
        /// Begins Monitoring a PRU event. This allows us to process asynchronous
        /// event actions. The pruEventHander will get the event information when
        /// the event is triggered.
        /// </summary>
        /// <param name="pruEventIn">the host event we begin monitoring</param>
        /// <param name="pruEventHander">the event data handler. Cannot be NULL</param>
        /// <history>
        ///    05 Jun 15  Cynic - Originally written
        /// </history>
        public void PRUEventMonitorOn(PRUEventEnum pruEventIn, PRUInterruptEventHandlerUIO pruEventHander)
        {
            if (enabledEvents[pruEventIn].EventFD == PRUEventHandler.NO_FD)
            {
                throw new Exception("Host Event Not Open");
            }
            if (pruEventHander == null)
            {
                throw new Exception("No Event Handler Supplied");
            }
            // set the handler now
            enabledEvents[pruEventIn].OnInterrupt += pruEventHander;
            // start the monitoring
            enabledEvents[pruEventIn].StartPRUEventMonitor();
        }

        /// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
        /// <summary>
        /// Stop monitoring a PRU event. 
        /// </summary>
        /// <param name="pruEventIn">the host event we stop monitoring</param>
        /// <history>
        ///    05 Jun 15  Cynic - Originally written
        /// </history>
        public void PRUEventMonitorOff(PRUEventEnum pruEventIn)
        {
            if (enabledEvents[pruEventIn].EventFD == PRUEventHandler.NO_FD)
            {
                throw new Exception("Host Event Not Open");
            }
            // start the monitoring
            enabledEvents[pruEventIn].StopPRUEventMonitor();
        }

        /// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
        /// <summary>
        /// Clears the specified event - this should be called after every event
        /// (synchronous or asynchronous) and must be done or the event cannot
        /// be re-triggered
        /// </summary>
        /// <param name="pruEventIn">the host event we clear</param>
        /// <history>
        ///    05 Jun 15  Cynic - Originally written
        /// </history>
        public void ClearEvent(PRUEventEnum pruEventIn)
        {
            byte[] fourDataBytes = new byte[sizeof(UInt32)];

            if (pruMappedMem == (IntPtr)(-1))
            {
                throw new Exception ("Could not reset PRU events, device file failed to open.");
            }              

            // get a pointer to the interrupt controller of the PRU as mapped by the UIO file
            IntPtr intcPtr = PRUUIOMappedINTCBase;
            if (intcPtr == (IntPtr)(-1))
            {
                throw new IOException ("Failed to get pointer to PRU INTC area");
            }

            // ########
            // ######## SECR0: Remove and clear the interrupt
            // ########

            // The System Interrupt Status Enabled/Clear Registers show the pending 
            // enabled status of the system interrupts. We write a 1 to the register
            // bit which represents the system interrupt. 

            // Note our standard configuration only uses System interrupts 16-31 so we only
            // need to deal with SECR0

            // put a 1 in position 0
            UInt32 secrResetVal = 0x00000001;
            // shift it up into position according to the system interrupt number, the
            // host event to system event correlation is entirely dependent on the 
            // standard configuration we use
            secrResetVal = secrResetVal << GetSystemEventNumberFromPruEvent(pruEventIn);
            BitConverter.GetBytes(secrResetVal).CopyTo(fourDataBytes,0);
            secrResetVal = 0xffffffff;
            // get a pointer to SECR0, and copy the data
            IntPtr secrPtr = new IntPtr(intcPtr.ToInt64() + (long)PRUOFFSET_INTC_SECR0);     
            Marshal.Copy(fourDataBytes, 0, secrPtr, fourDataBytes.Length);
            // we can ignore SECR1, since we are ignoring any system interrupts it configures
 
            // ########
            // ######## HIEISR
            // ########

            // get the number host interrupt which corresponts to the pruEvent
            // writing that number to the HIEISR will re-enable the interrupt
            // we have to do this after clearing the interrupt or it will just
            // cause the old interrupt to be re-triggered.
            UInt32 hieisrSetVal = GetHostEventFromPruEvent(pruEventIn);
            BitConverter.GetBytes(hieisrSetVal).CopyTo(fourDataBytes,0);
            IntPtr hieisrPtr = new IntPtr(intcPtr.ToInt64() + (long)PRUOFFSET_INTC_HIEISR);     
            Marshal.Copy(fourDataBytes, 0, hieisrPtr, fourDataBytes.Length);

        }
            
        /// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
        /// <summary>
        /// Resets the events for all PRU's (PRU0 and PRU1).
        /// 
        /// The PRU's share a interrupt controller subsystem so calling this 
        /// function resets the interrupts and events for both PRUs
        /// 
        /// <history>
        ///    05 Jun 15  Cynic - Originally written
        /// </history>
        public void ResetInterruptsForAllPRUs()
        {
            byte[] fourDataBytes = new byte[sizeof(UInt32)];

            if (pruMappedMem == (IntPtr)(-1))
            {
                throw new Exception ("Could not reset PRU events, device file failed to open.");
            }              

            // get a pointer to the interrupt controller of the PRU as mapped by the UIO file
            IntPtr intcPtr = PRUUIOMappedINTCBase;
            if (intcPtr == (IntPtr)(-1))
            {
                throw new IOException ("Failed to get pointer to PRU INTC area");
            }

            // NOTE: Since this setup code is not particularly performance critical 
            // and I wish to illustrate what is going on. I am explicitly coding the setup 
            // for the registers rather than just using one call to update all of them or
            // using some sort of clever loop which builds the offsets

            // begin the disable. The code starts by disabling at the most global
            // level and gets more and more specific on the reset in order to 
            // prevent a reset inadvertently triggering an interrupt

            // ########
            // ######## GER: Disable all interrupts - host or PRU system
            // ########

            // The Global Host Interrupt Enable Register enables all the host interrupts. 
            // Individual host interrupts are still enabled or disabled from their individual 
            // enables and are not overridden by the global enable.

            // this is just set to 0x01 to enable and 0x00 to disable

            // set up a suitable data structure
            UInt32 gerResetVal = 0x00000000;
            BitConverter.GetBytes(gerResetVal).CopyTo(fourDataBytes,0);
            // get a pointer to GER, and copy the data
            IntPtr gerPtr = new IntPtr(intcPtr.ToInt64() + (long)PRUOFFSET_INTC_GER);     
            Marshal.Copy(fourDataBytes, 0, gerPtr, fourDataBytes.Length);

            // ########
            // ######## HIDSR: Prevent host interrupts from triggering
            // ########

            // The Host Interrupt Enable Indexed Clear Register disables a host interrupt output. 
            // The host 0-9 interrupt to disable is the bit position written. This disables the host 
            // interrupt output. Only the bottom 10 bits are used

            // since we are resetting, we wish to disable all host interrupts, so we write a
            // a series of values 0 to 10 here

             // get a pointer to HIDISR, and copy the data
            // NOTE one wonders why it is not called the HIEICR register to remain consistent
            //      with the other naming conventions. The HIEISR register is used to re-enable
            IntPtr hidisrPtr = new IntPtr(intcPtr.ToInt64() + (long)PRUOFFSET_INTC_HIDISR);     
            for (UInt32 hidisrResetVal = MIN_HOSTINT; hidisrResetVal <= MAX_HOSTINT; hidisrResetVal++)
            {
                BitConverter.GetBytes(hidisrResetVal).CopyTo(fourDataBytes, 0);
                Marshal.Copy(fourDataBytes, 0, hidisrPtr, fourDataBytes.Length);
            }

            // ########
            // ######## EICR: Prevent system interrupts from triggering
            // ########

            // The System Interrupt Enable Indexed Clear Register allows disabling a system interrupt. 
            // This the index value written. This clears the Enable Register bit of the given index.
            // Only the bottom 10 bits are used

            // since we are resetting, we wish to disable all host interrupts, so we write a
            // a series of values 0 to 61 here

            // get a pointer to EICR
            IntPtr eicrPtr = new IntPtr(intcPtr.ToInt64() + (long)PRUOFFSET_INTC_EICR);     
            for (UInt32 eicrResetVal = MIN_SYSINT; eicrResetVal <= MAX_SYSINT; eicrResetVal++)
            {
                BitConverter.GetBytes(eicrResetVal).CopyTo(fourDataBytes, 0);
                Marshal.Copy(fourDataBytes, 0, eicrPtr, fourDataBytes.Length);
            }

            // ########
            // ######## ECR: Prevent system interrupts from mapping to channels
            // ########

            // The System Interrupt Enable Clear Registers disable system interrupts mapping to 
            // channels. System interrupts that have a disabled channel mapping do not interrupt 
            // the host. There is a bit per system interrupt. We disable all system interrupt 
            // mappings by writing a 1 in each bit position

            // set up a suitable data structure
            UInt32 ecrResetVal = 0xffffffff;
            BitConverter.GetBytes(ecrResetVal).CopyTo(fourDataBytes,0);

            // get a pointer to ECR0, and copy the data
            IntPtr ecrPtr = new IntPtr(intcPtr.ToInt64() + (long)PRUOFFSET_INTC_ECR0);     
            Marshal.Copy(fourDataBytes, 0, ecrPtr, fourDataBytes.Length);
            // get a pointer to ECR1, and copy the data
            ecrPtr = new IntPtr(intcPtr.ToInt64() + (long)PRUOFFSET_INTC_ECR1);     
            Marshal.Copy(fourDataBytes, 0, ecrPtr, fourDataBytes.Length);

            // ########
            // ######## SECR: remove and clear any interrupts which may be pending
            // ########

            // The System Interrupt Status Enabled/Clear Registers show the pending 
            // enabled status of the system interrupts. There is 1 bit per system interrupt
            // We write a 1 to each bit of these registers so that we clear all system interrupts 

            // set up a suitable data structure
            UInt32 secrResetVal = 0xffffffff;
            BitConverter.GetBytes(secrResetVal).CopyTo(fourDataBytes,0);

            // get a pointer to SECR0, and copy the data
            IntPtr secrPtr = new IntPtr(intcPtr.ToInt64() + (long)PRUOFFSET_INTC_SECR0);     
            Marshal.Copy(fourDataBytes, 0, secrPtr, fourDataBytes.Length);
            // get a pointer to SECR1, and copy the data
            secrPtr = new IntPtr(intcPtr.ToInt64() + (long)PRUOFFSET_INTC_SECR1);     
            Marshal.Copy(fourDataBytes, 0, secrPtr, fourDataBytes.Length);
 
            // ########
            // ######## HMR[0-2]: reset the mapping of the channels to the Host Interrupts
            // ########

            // The Host Interrupt Map Registers define the host interrupts for channels. 
            // There is one register per 4 channels. We init these all to 0 now which means
            // all channels are mapped to a host interrupt 0. A more appropriate mapping is
            // performed later in our standard setup.  

            UInt32 hmrResetVal = 0x00000000;
            BitConverter.GetBytes(hmrResetVal).CopyTo(fourDataBytes,0);
            // get a pointer to the HMR regs, and copy the data
            IntPtr hmrPtr = new IntPtr(intcPtr.ToInt64() + (long)PRUOFFSET_INTC_HMR0);     
            Marshal.Copy(fourDataBytes, 0, hmrPtr, fourDataBytes.Length);
            hmrPtr = new IntPtr(intcPtr.ToInt64() + (long)PRUOFFSET_INTC_HMR1);     
            Marshal.Copy(fourDataBytes, 0, hmrPtr, fourDataBytes.Length);
            hmrPtr = new IntPtr(intcPtr.ToInt64() + (long)PRUOFFSET_INTC_HMR2);     
            Marshal.Copy(fourDataBytes, 0, hmrPtr, fourDataBytes.Length);

            // ########
            // ######## CMR[00-63]: Reset the mapping of the system interrupts to the channels
            // ########

            // The 16 Channel Map Registers specify the channel for the system interrupts 0 to 63. 
            // There is one register per 4 system interrupts.

            // all we are doing here is resetting all 16 CMR regs to 0). This means
            // all system interrupts are mapped to a channel 0. A more appropriate mapping is
            // performed later in our standard setup.  

            UInt32 cmrResetVal = 0x00000000;
            BitConverter.GetBytes(cmrResetVal).CopyTo(fourDataBytes,0);
            // get a pointer to the CMR regs, and copy the data
            IntPtr cmrPtr = new IntPtr(intcPtr.ToInt64() + (long)PRUOFFSET_INTC_CMR00);     
            Marshal.Copy(fourDataBytes, 0, cmrPtr, fourDataBytes.Length);
            cmrPtr = new IntPtr(intcPtr.ToInt64() + (long)PRUOFFSET_INTC_CMR01);     
            Marshal.Copy(fourDataBytes, 0, cmrPtr, fourDataBytes.Length);
            cmrPtr = new IntPtr(intcPtr.ToInt64() + (long)PRUOFFSET_INTC_CMR02);     
            Marshal.Copy(fourDataBytes, 0, cmrPtr, fourDataBytes.Length);
            cmrPtr = new IntPtr(intcPtr.ToInt64() + (long)PRUOFFSET_INTC_CMR03);     
            Marshal.Copy(fourDataBytes, 0, cmrPtr, fourDataBytes.Length);
            cmrPtr = new IntPtr(intcPtr.ToInt64() + (long)PRUOFFSET_INTC_CMR04);     
            Marshal.Copy(fourDataBytes, 0, cmrPtr, fourDataBytes.Length);
            cmrPtr = new IntPtr(intcPtr.ToInt64() + (long)PRUOFFSET_INTC_CMR05);     
            Marshal.Copy(fourDataBytes, 0, cmrPtr, fourDataBytes.Length);
            cmrPtr = new IntPtr(intcPtr.ToInt64() + (long)PRUOFFSET_INTC_CMR06);     
            Marshal.Copy(fourDataBytes, 0, cmrPtr, fourDataBytes.Length);
            cmrPtr = new IntPtr(intcPtr.ToInt64() + (long)PRUOFFSET_INTC_CMR07);     
            Marshal.Copy(fourDataBytes, 0, cmrPtr, fourDataBytes.Length);
            cmrPtr = new IntPtr(intcPtr.ToInt64() + (long)PRUOFFSET_INTC_CMR08);     
            Marshal.Copy(fourDataBytes, 0, cmrPtr, fourDataBytes.Length);
            cmrPtr = new IntPtr(intcPtr.ToInt64() + (long)PRUOFFSET_INTC_CMR09);     
            Marshal.Copy(fourDataBytes, 0, cmrPtr, fourDataBytes.Length);
            cmrPtr = new IntPtr(intcPtr.ToInt64() + (long)PRUOFFSET_INTC_CMR10);     
            Marshal.Copy(fourDataBytes, 0, cmrPtr, fourDataBytes.Length);
            cmrPtr = new IntPtr(intcPtr.ToInt64() + (long)PRUOFFSET_INTC_CMR11);     
            Marshal.Copy(fourDataBytes, 0, cmrPtr, fourDataBytes.Length);
            cmrPtr = new IntPtr(intcPtr.ToInt64() + (long)PRUOFFSET_INTC_CMR12);     
            Marshal.Copy(fourDataBytes, 0, cmrPtr, fourDataBytes.Length);
            cmrPtr = new IntPtr(intcPtr.ToInt64() + (long)PRUOFFSET_INTC_CMR13);     
            Marshal.Copy(fourDataBytes, 0, cmrPtr, fourDataBytes.Length);
            cmrPtr = new IntPtr(intcPtr.ToInt64() + (long)PRUOFFSET_INTC_CMR14);     
            Marshal.Copy(fourDataBytes, 0, cmrPtr, fourDataBytes.Length);
            cmrPtr = new IntPtr(intcPtr.ToInt64() + (long)PRUOFFSET_INTC_CMR15);     
            Marshal.Copy(fourDataBytes, 0, cmrPtr, fourDataBytes.Length);

            // ########
            // ######## SIPR[0-1]: Reset the interrupt polarity settings
            // ######## 

            // The System Interrupt Polarity Registers define the polarity of the system interrupts
            // We always want active high interrupts so we write a 1 to each bit in both SIPR registers.

            // set up a suitable data structure
            UInt32 siprResetVal = 0xffffffff;
            BitConverter.GetBytes(siprResetVal).CopyTo(fourDataBytes,0);

            // get a pointer to SIPR0, and copy the data
            IntPtr siprPtr = new IntPtr(intcPtr.ToInt64() + (long)PRUOFFSET_INTC_SIPR0);     
            Marshal.Copy(fourDataBytes, 0, siprPtr, fourDataBytes.Length);
            // get a pointer to SIPR1, and copy the data
            siprPtr = new IntPtr(intcPtr.ToInt64() + (long)PRUOFFSET_INTC_SIPR1);     
            Marshal.Copy(fourDataBytes, 0, siprPtr, fourDataBytes.Length);

            // ########
            // ######## SITR[0-1]: Reset the interrupt type settings
            // ######## 

            // The System Interrupt Type Registers define the type of the system interrupts. 
            // The type of all system interrupts must be pulse. We always write 0 to the bits 
            // of these registers. 

            // set up a suitable data structure
            UInt32 sitrResetVal = 0x00000000;
            BitConverter.GetBytes(sitrResetVal).CopyTo(fourDataBytes,0);

            // get a pointer to SITR0, and copy the data
            IntPtr sitrPtr = new IntPtr(intcPtr.ToInt64() + (long)PRUOFFSET_INTC_SITR0);     
            Marshal.Copy(fourDataBytes, 0, sitrPtr, fourDataBytes.Length);
            // get a pointer to SITR1, and copy the data
            sitrPtr = new IntPtr(intcPtr.ToInt64() + (long)PRUOFFSET_INTC_SITR1);     
            Marshal.Copy(fourDataBytes, 0, sitrPtr, fourDataBytes.Length);


            // ########
            // ######## CR: Disable all nesting
            // ########

            // The Global Host Interrupt Enable Register enables all the host interrupts. 
            // Individual host interrupts are still enabled or disabled from their individual 
            // enables and are not overridden by the global enable.

            // this is just set to 0x01 to enable and 0x00 to disable

            // set up a suitable data structure
            UInt32 crResetVal = 0x00000000;
            BitConverter.GetBytes(crResetVal).CopyTo(fourDataBytes,0);
            // get a pointer to CR, and copy the data
            IntPtr crPtr = new IntPtr(intcPtr.ToInt64() + (long)PRUOFFSET_INTC_CR);     
            Marshal.Copy(fourDataBytes, 0, crPtr, fourDataBytes.Length);
        }

        /// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
        /// <summary>
        /// The address of the PRUs (0 or 1) control register as mapped
        /// into the main user space memory by the UIO file we opened
        /// </summary>
        /// <history>
        ///    05 Jun 15  Cynic - Originally written
        /// </history>
        private IntPtr PRUUIOMappedControlBase
        {
            get
            {
                if (PRUID == PRUEnum.PRU_0)
                {
                    return GetPRUUIOMappedPointer(PRUOFFSET_CONTROL_PRU0);
                }
                else if (PRUID == PRUEnum.PRU_1)
                {
                    return GetPRUUIOMappedPointer(PRUOFFSET_CONTROL_PRU1);
                }
                else
                {
                    throw new Exception("Unknown PRU: "+ PRUID.ToString());
                }                    
            }
        }

        /// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
        /// <summary>
        /// The address of the PRUs (0 or 1) Data RAM as mapped
        /// into the main user space memory by the UIO file we opened
        /// </summary>
        /// <history>
        ///    05 Jun 15  Cynic - Originally written
        /// </history>
        private IntPtr PRUUIOMappedDRAMBase
        {
            get
            {
                if (PRUID == PRUEnum.PRU_0)
                {
                    return GetPRUUIOMappedPointer(PRUOFFSET_DRAM_PRU0);
                }
                else if (PRUID == PRUEnum.PRU_1)
                {
                    return GetPRUUIOMappedPointer(PRUOFFSET_DRAM_PRU1);
                }
                else
                {
                    throw new Exception("Unknown PRU: "+ PRUID.ToString());
                }                    
            }
        }

        /// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
        /// <summary>
        /// The address of the PRUs (0 or 1) Interrupt Controller (INTC) as mapped
        /// into the main user space memory by the UIO file we opened. The two
        /// PRUs use the same INTC so this value will be the same for both
        /// </summary>
        /// <history>
        ///    05 Jun 15  Cynic - Originally written
        /// </history>
        private IntPtr PRUUIOMappedINTCBase
        {
            get
            {
                if (PRUID == PRUEnum.PRU_0)
                {
                    return GetPRUUIOMappedPointer(PRUOFFSET_INTC_PRU0);
                }
                else if (PRUID == PRUEnum.PRU_1)
                {
                    return GetPRUUIOMappedPointer(PRUOFFSET_INTC_PRU1);
                }
                else
                {
                    throw new Exception("Unknown PRU: "+ PRUID.ToString());
                }                    
            }
        }

        /// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
        /// <summary>
        /// The address of the PRUs (0 or 1) Instruction RAM as mapped
        /// into the main user space memory by the UIO file we opened
        /// </summary>
        /// <history>
        ///    05 Jun 15  Cynic - Originally written
        /// </history>
        private IntPtr PRUUIOMappedIRAMBase
        {
            get
            {
                if (PRUID == PRUEnum.PRU_0)
                {
                    return GetPRUUIOMappedPointer(PRUOFFSET_IRAM_PRU0);
                }
                else if (PRUID == PRUEnum.PRU_1)
                {
                    return GetPRUUIOMappedPointer(PRUOFFSET_IRAM_PRU1);
                }
                else
                {
                    throw new Exception("Unknown PRU: "+ PRUID.ToString());
                }                    
            }
        }

        /// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
        /// <summary>
        /// Gets the pointer for a memory mapped UIO file
        /// </summary>
        /// <param name="offsetIntoMap">the offset into the UIO memory mapped
        /// file to return
        /// <history>
        ///    05 Jun 15  Cynic - Originally written
        /// </history>
        private IntPtr GetPRUUIOMappedPointer(long offsetIntoMap)
        {
            if (pruMappedMem == (IntPtr)(-1))
            {
                throw new IOException ("no mmap for pru device file");
            }

            // build a pointer to the mapped file
            return new IntPtr(pruMappedMem.ToInt64() + offsetIntoMap);     
        }           

        /// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
        /// <summary>
        /// Close the memory maps. 
        /// </summary>
        /// <history>
        ///    05 Jun 15  Cynic - Originally written
        /// </history>
        private void CloseMemoryMaps()
        {
            try
            {
                //Console.WriteLine("Closing PRU Memory Maps"); 
                // clean up the pru map
                if (pruMappedMem != IntPtr.Zero) Syscall.munmap(pruMappedMem, pruPhysMapSize);
                pruMappedMem = IntPtr.Zero;
            }
            catch {}
        }

        /// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
        /// <summary>
        /// Closes all events
        /// </summary>
        /// <history>
        ///    05 Jun 15  Cynic - Originally written
        /// </history>
        public void CloseAllEvents()
        {
            foreach (KeyValuePair<PRUEventEnum, PRUEventHandler> kvPairObj in enabledEvents)
            {
                // close the event, will ignore it if already closed
                CloseEvent(kvPairObj.Key);
            }
        }
         
        /// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
        /// <summary>
        /// Closes an event
        /// </summary>
        /// <param name="pruEventIn">the host event we close</param>
        /// <history>
        ///    05 Jun 15  Cynic - Originally written
        /// </history>
        public void CloseEvent(PRUEventEnum pruEventIn)
        {
            if (enabledEvents.ContainsKey(pruEventIn) == false) return;
            // stop the worker thread - if there is one
            enabledEvents[pruEventIn].StopPRUEventMonitor();
            // if we have an open event file then close it
            if (enabledEvents[pruEventIn].EventFD != PRUEventHandler.NO_FD)
            {
                Syscall.close(enabledEvents[pruEventIn].EventFD);
                enabledEvents[pruEventIn].EventFD = PRUEventHandler.NO_FD;
            }
        }

        /// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
        /// <summary>
        /// Gets the PRUUIODevice as a number
        /// </summary>
        /// <param name="eventIn">The event we convert</param>
        /// <returns>>the uio device enum or exception for fail</returns>
        /// <history>
        ///    05 Jun 15  Cynic - Originally written
        /// </history>
        private PRUUIODeviceEnum GetPruUIODeviceFromPRUEvent(PRUEventEnum eventIn)
        {
            switch (eventIn)
            {
            case PRUEventEnum.PRU_EVENT_0:
                return PRUUIODeviceEnum.PRUUIODEVICE_0;
            case PRUEventEnum.PRU_EVENT_1:
                return PRUUIODeviceEnum.PRUUIODEVICE_1;
            case PRUEventEnum.PRU_EVENT_2:
                return PRUUIODeviceEnum.PRUUIODEVICE_2;
            case PRUEventEnum.PRU_EVENT_3:
                return PRUUIODeviceEnum.PRUUIODEVICE_3;
            case PRUEventEnum.PRU_EVENT_4:
                return PRUUIODeviceEnum.PRUUIODEVICE_4;
            case PRUEventEnum.PRU_EVENT_5:
                return PRUUIODeviceEnum.PRUUIODEVICE_5;
            case PRUEventEnum.PRU_EVENT_6:
                return PRUUIODeviceEnum.PRUUIODEVICE_6;
            case PRUEventEnum.PRU_EVENT_7:
                return PRUUIODeviceEnum.PRUUIODEVICE_7;
            default:
                {
                    throw new Exception("no such PRU Event: " + eventIn.ToString());
                }
            }
        }

        /// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
        /// <summary>
        /// Gets a Host Event number from the PRU Event
        /// </summary>
        /// <param name="eventIn">The event we convert</param>
        /// <returns>>the event as a number or exception for fail</returns>
        /// <history>
        ///    05 Jun 15  Cynic - Originally written
        /// </history>
        private uint GetHostEventFromPruEvent(PRUEventEnum eventIn)
        {
            switch (eventIn)
            {
            // host events 0 and 1 do not exist to user space programs. They are used for 
            // PRU to PRU interrupts
            case PRUEventEnum.PRU_EVENT_0:
                return 2;
            case PRUEventEnum.PRU_EVENT_1:
                return 3;
            case PRUEventEnum.PRU_EVENT_2:
                return 4;
            case PRUEventEnum.PRU_EVENT_3:
                return 5;
            case PRUEventEnum.PRU_EVENT_4:
                return 6;
            case PRUEventEnum.PRU_EVENT_5:
                return 7;
            default:
                {
                    throw new Exception("no such PRU Event: " + eventIn.ToString());
                }
            }
        }

        /// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
        /// <summary>
        /// Gets a system event number as a number. Note this correlation
        /// is only valid if we are using the standard configuration setup.
        /// </summary>
        /// <param name="eventIn">The event we convert</param>
        /// <returns>>the system event number as a number or exception for fail</returns>
        /// <history>
        ///    05 Jun 15  Cynic - Originally written
        /// </history>
        private int GetSystemEventNumberFromPruEvent(PRUEventEnum eventIn)
        {
            switch (eventIn)
            {
            case PRUEventEnum.PRU_EVENT_0:
                return 16;
            case PRUEventEnum.PRU_EVENT_1:
                return 17;
            case PRUEventEnum.PRU_EVENT_2:
                return 18;
            case PRUEventEnum.PRU_EVENT_3:
                return 18;
            case PRUEventEnum.PRU_EVENT_4:
                return 20;
            case PRUEventEnum.PRU_EVENT_5:
                return 21;
            case PRUEventEnum.PRU_EVENT_6:
                return 22;
            case PRUEventEnum.PRU_EVENT_7:
                return 23;
            default:
                {
                    throw new Exception("no such PRU Event: " + eventIn.ToString());
                }
            }
        }

        /// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
        /// <summary>
        /// Gets the PRUUIODevice as a number
        /// </summary>
        /// <param name="eventIn">The event we convert</param>
        /// <returns>>the uio device number</returns>
        /// <history>
        ///    05 Jun 15  Cynic - Originally written
        /// </history>
        private int GetPruUIODeviceAsNumber(PRUEventEnum eventIn)
        {
            return GetPruUIODeviceAsNumber(GetPruUIODeviceFromPRUEvent(eventIn));
        }

        /// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
        /// <summary>
        /// Gets the PRUUIODevice as a number
        /// </summary>
        /// <param name="pruUIODeviceIn">the device we find the number for</param>
        /// <returns>>the device number or exception for fail</returns>
        /// <history>
        ///    05 Jun 15  Cynic - Originally written
        /// </history>
        private int GetPruUIODeviceAsNumber(PRUUIODeviceEnum pruUIODeviceIn)
        {
            switch (pruUIODeviceIn)
            {
            case PRUUIODeviceEnum.PRUUIODEVICE_0:
                return 0;
            case PRUUIODeviceEnum.PRUUIODEVICE_1:
                return 1;
            case PRUUIODeviceEnum.PRUUIODEVICE_2:
                return 2;
            case PRUUIODeviceEnum.PRUUIODEVICE_3:
                return 3;
            case PRUUIODeviceEnum.PRUUIODEVICE_4:
                return 4;
            case PRUUIODeviceEnum.PRUUIODEVICE_5:
                return 5;
            case PRUUIODeviceEnum.PRUUIODEVICE_6:
                return 6;
            case PRUUIODeviceEnum.PRUUIODEVICE_7:
                return 7;
            default:
                {
                    throw new Exception("no such PRU UIO Device: " + PRUUIODeviceEnum.PRUUIODEVICE_0.ToString());
                }
            }
        }

        /// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
        /// <summary>
        /// Gets the ID of a pru as a number
        /// </summary>
        /// <returns>>the pru id or exception for fail</returns>
        /// <history>
        ///    05 Jun 15  Cynic - Originally written
        /// </history>
        private int GetPruIDAsNumber()
        {
            switch (pruID)
            {
            case PRUEnum.PRU_0:
                return 0;
            case PRUEnum.PRU_1:
                return 1;
            default:
                {
                    throw new Exception("no such PRU ID: " + pruID.ToString());
                }
            }
        }
            
        /// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
        /// <summary>
        /// Gets the ID of the PRU we are using
        /// </summary>
        /// <history>
        ///    05 Jun 15  Cynic - Originally written
        /// </history>
        public PRUEnum PRUID
        {
            get
            {
                return pruID;
            }
        }
            
        // #########################################################################
        // ### Dispose Code
        // #########################################################################
        #region

        /// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
        /// <summary>
        /// Gets the disposed state. There is no setter - this is done inside the 
        /// Dispose() call.
        /// </summary>
        /// <history>
        ///    05 Jun 15 Cynic - Originally written
        /// </history>
        public bool Disposed
        {
            get
            {
                return disposed;
            }
        }

        /// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
        /// <summary>
        /// Implement IDisposable. 
        ///     Do not make this method virtual. 
        ///     A derived class should not be able to override this method. 
        ///  see: http://msdn.microsoft.com/en-us/library/system.idisposable.dispose%28v=vs.110%29.aspx
        /// </summary>
        /// <history>
        ///    05 Jun 15 Cynic - Originally written
        /// </history>
        public void Dispose()
        {
            Dispose(true);
            // Suppress finalization.
            GC.SuppressFinalize(this);
        }

        /// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
        /// <summary>
        /// Implement IDisposable. 
        /// Dispose(bool disposing) executes in two distinct scenarios. 
        /// 
        ///    If disposing equals true, the method has been called directly 
        ///    or indirectly by a user's code. Managed and unmanaged resources 
        ///    can be disposed.
        ///  
        ///    If disposing equals false, the method has been called by the 
        ///    runtime from inside the finalizer and you should not reference 
        ///    other objects. Only unmanaged resources can be disposed. 
        /// 
        ///  see: http://msdn.microsoft.com/en-us/library/system.idisposable.dispose%28v=vs.110%29.aspx
        /// 
        /// </summary>
        /// <history>
        ///    05 Jun 15 Cynic - Originally written
        /// </history>
        protected virtual void Dispose(bool disposing)
        {
            // Check to see if Dispose has already been called. 
            if(Disposed==false)
            {
                // If disposing equals true, dispose all managed 
                // and unmanaged resources. 
                if(disposing==true)
                {
                    // Dispose managed resources.
                }

                // Call the appropriate methods to clean up 
                // unmanaged resources here. If disposing is false, 
                // only the following code is executed.
                CloseAllEvents();
                CloseMemoryMaps();

                // Note disposing has been done.
                disposed = true;

            }
        }
        #endregion

    }
}

HTML Code Generated by CoDocker v00.90 Beta