/*
* 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
}
}