/* * Util.ParallelPortWriter.cs * Authors: Adam Nabinger * Copyright (c) 2007-2008 Cornell University This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ using System; using System.Collections.Generic; using System.Diagnostics; using System.Threading; namespace Util { /// /// Access to write individual bytes of data to the parallel port. /// public sealed class ParallelPortWriter : IDisposable { #region Enums /// /// An Internal flag to holding the current state of the parallel port. /// private enum ModeFlags { /// /// The parallel port is currently being held low, and the duration has passed since the transition. /// So any data received may be written immediately. /// Waiting, /// /// The parallel port is currently being held high with a data write. /// High, /// /// The parallel port is currently being held low immediately following a data write. /// Low } #endregion #region Constants /// /// The identifier for the driver file. /// internal const string DriverFile = @"inpout32.dll"; /// /// The duration to hold each high and low written, in milliseconds. /// private const float DurationMilliseconds = 5.0f; /// /// How long the thread should sleep in between checks while holding the port for DurationMilliseconds. /// Due to the nature of threading, this is not an absolute amount of time, but rather a request. /// It may be less time, or more, before the thread is reawakened. /// So this value should be small, to ensure the actual time is close to the specified duration. /// private const int SleepMilliseconds = 1; #endregion #region Fields /// /// The duration, in CPU ticks. This will vary based on the speed of the processor, /// but is faster and more accurate to check than clock time. /// private static readonly long DurationTicks = (long)(Stopwatch.Frequency * DurationMilliseconds / 1000.0); /// /// Internal count of how many live references there are to the parallel port writer. /// The final dispose call will block until all pending data is written. /// private static int references; /// /// The thread that will do all of the writing. /// private static Thread WorkerThread; /// /// The Queue for storing the pending data to be written. /// private static Queue MyQueue; /// /// The current status of the parallel port. /// private static ModeFlags Status = ModeFlags.Waiting; /// /// The CPU tick time at which the next transition may take place. /// private static long NextTransition; /// /// Should the background worker currently be running. /// private static bool run; /// /// Has this writer been initialized. /// private bool initialized; /// /// Has this writer been disposed. /// private bool disposed; #endregion #region Initialization /// /// Initialize this device to be ready to recieve data. /// public void Initialize() { if (initialized) { return; } initialized = true; if (++references != 1) { return; } MyQueue = new Queue(); run = true; WorkerThread = new Thread(ParallelPortBackgroundThread); WorkerThread.Priority = ThreadPriority.AboveNormal; WorkerThread.Name = "ParallelPortWriter WorkerThread"; WorkerThread.Start(); strobe(false); } #endregion #region Management /// /// Dispose of this device, to write all pending data. /// public void Dispose() { if (!initialized || disposed) { return; } disposed = true; if (--references != 0) { return; } run = false; lock (WorkerThread) { Monitor.Pulse(WorkerThread); } WorkerThread.Join(); } #endregion #region Read/Write /// /// Reads in a bye of data from the parallel port /// /// The byte currently stored in the parallel port internal static byte read() { return NativeMethods.Inp32((short)Settings.ParallelPort); } /// /// Writes a byte of data to the parallel port /// /// The byte to write to the parallel port private static void write(byte data) { NativeMethods.Out32((short)Settings.ParallelPort, data); } /// /// Writes this code out the parallel port. /// /// The numeric event code to right out. public void Write(byte code) { #if DEBUG if (!initialized || disposed) { throw new InvalidOperationException("Writer was not initialized, or was disposed."); } #endif MyQueue.Enqueue(code); lock (WorkerThread) { Monitor.Pulse(WorkerThread); } } #endregion #region Strobe /// /// Set the strobe pin, according to documentation. /// Untested. /// /// Should the strobe be low, as when writing data. private static void strobe(bool low) { if (low) { NativeMethods.Out32((short)(Settings.ParallelPort + 2), (byte)(NativeMethods.Inp32((short)(Settings.ParallelPort + 2)) & 0xFE)); } else { NativeMethods.Out32((short)(Settings.ParallelPort + 2), (byte)(NativeMethods.Inp32((short)(Settings.ParallelPort + 2)) | 0x01)); } } #endregion #region Writer Thread /// /// The code for the background thread to run. /// private static void ParallelPortBackgroundThread() { do { lock (WorkerThread) { // Wait until pulsed to indicate some data is waiting to be written. if(run) Monitor.Wait(WorkerThread); } // The thread may be awoken early, so check. if (MyQueue.Count <= 0) { continue; } // If there is data, write it. #if DEBUG Console.WriteLine("Parallel Port Output:\t" + MyQueue.Dequeue()); #else write(MyQueue.Dequeue()); #endif strobe(true); // Next Transition is the earliest point we should change the parallel port to a different value. NextTransition = Stopwatch.GetTimestamp() + DurationTicks; // Set our status to remember we are currently writing to the port. Status = ModeFlags.High; do { switch (Status) { case ModeFlags.High: // Sleep until the NextTransition time has passed. while (Stopwatch.GetTimestamp() < NextTransition) { Thread.Sleep(SleepMilliseconds); } // Reset to 0 and hold. strobe(false); write(0); NextTransition = Stopwatch.GetTimestamp() + DurationTicks; Status = ModeFlags.Low; break; case ModeFlags.Low: if (MyQueue.Count > 0) { while (Stopwatch.GetTimestamp() < NextTransition) { Thread.Sleep(SleepMilliseconds); } #if DEBUG Console.WriteLine("Parallel Port Output:\t" + MyQueue.Dequeue()); #else write(MyQueue.Dequeue()); #endif strobe(true); NextTransition = Stopwatch.GetTimestamp() + DurationTicks; Status = ModeFlags.High; } else { while (Stopwatch.GetTimestamp() < NextTransition) { Thread.Sleep(SleepMilliseconds); } // Another event may have been queued while we were sleeping, check before waiting. if (MyQueue.Count > 0) { #if DEBUG Console.WriteLine("Parallel Port Output:\t" + MyQueue.Dequeue()); #else write(MyQueue.Dequeue()); #endif strobe(true); NextTransition = Stopwatch.GetTimestamp() + DurationTicks; Status = ModeFlags.High; } else // No events in the queue, we can go back to waiting. { Status = ModeFlags.Waiting; } } break; } } while (Status != ModeFlags.Waiting); } while (run); } #endregion } }