Tech Off Post

Single Post Permalink

View Thread: Stumbling on Basic Serial Port IO
  • User profile image
    ploe

    ploe said:
    ScanIAm said:
    *snip*

    Thanks. I knew it was going to be something little. I appended the equivalent of '\r\n' to the command I was sending at it wrote back the command I sent plus the response. Smiley

     

    Edit: It was a GPRS/GSM device.

    So people don't have to re-invent the wheel, this was the wrapper I ended up writing...may be of use to someone.

     

    EDIT: code formatter doesn't work too well :/

     

        /// <summary>
        /// Class that allows for serial communication in Windows.
        /// </summary>
        public class SerialCom : IDisposable
        {
            #region Fields
     
            /// <summary>
            /// The baud rate at which the communications device operates.
            /// </summary>
            private readonly int baudRate;
     
            /// <summary>
            /// The number of bits in the bytes to be transmitted and received.
            /// </summary>
            private readonly byte byteSize;
     
            /// <summary>
            /// The system handle to the serial port connection ('file' handle).
            /// </summary>
            private IntPtr handle = IntPtr.Zero;
     
            /// <summary>
            /// The parity scheme to be used.
            /// </summary>
            private readonly Parity parity;
     
            /// <summary>
            /// The name of the serial port to connect to.
            /// </summary>
            private readonly string portName;
     
            /// <summary>
            /// The number of bits in the bytes to be transmitted and received.
            /// </summary>
            private readonly StopBits stopBits;
     
            #endregion
     
            /// <summary>
            /// Creates a new instance of SerialCom.
            /// </summary>
            /// <param>The name of the serial port to connect to</param>
            /// <param>The baud rate at which the communications device operates</param>
            /// <param>The number of stop bits to be used</param>
            /// <param>The parity scheme to be used</param>
            /// <param>The number of bits in the bytes to be transmitted and received</param>
            public SerialCom(string portName, int baudRate, StopBits stopBits, Parity parity, byte byteSize)
            {
                if (stopBits == StopBits.None)
                    throw new ArgumentException("stopBits cannot be StopBits.None", "stopBits");
                if (byteSize < 5 || byteSize > 8)
                    throw new ArgumentOutOfRangeException("The number of data bits must be 5 to 8 bits.", "byteSize");
                if (baudRate < 110 || baudRate > 256000)
                    throw new ArgumentOutOfRangeException("Invalid baud rate specified.", "baudRate");
                if ((byteSize == 5 && stopBits == StopBits.Two) || (stopBits == StopBits.OnePointFive && byteSize > 5))
                    throw new ArgumentException("The use of 5 data bits with 2 stop bits is an invalid combination, " +
                        "as is 6, 7, or 8 data bits with 1.5 stop bits.");
     
                this.portName = portName;
                this.baudRate = baudRate;
                this.byteSize = byteSize;
                this.stopBits = stopBits;
                this.parity = parity;
            }
     
            /// <summary>
            /// Creates a new instance of SerialCom.
            /// </summary>
            /// <param>The name of the serial port to connect to</param>
            /// <param>The baud rate at which the communications device operates</param>
            /// <param>The number of stop bits to be used</param>
            /// <param>The parity scheme to be used</param>
            public SerialCom(string portName, int baudRate, StopBits stopBits, Parity parity)
                : this(portName, baudRate, stopBits, parity, 8) { }
     
            /// <summary>
            /// Disconnects and disposes of the SerialCom instance.
            /// </summary>
            public void Dispose()
            {
                if (handle != IntPtr.Zero)
                {
                    CloseHandle(handle);
                    handle = IntPtr.Zero;
                }
            }
     
            /// <summary>
            /// Flushes the serial I/O buffers.
            /// </summary>
            /// <returns>Whether or not the operation succeeded</returns>
            public bool Flush()
            {
                FailIfNotConnected();
     
                const int PURGE_RXCLEAR = 0x0008; // input buffer
                const int PURGE_TXCLEAR = 0x0004; // output buffer
                return PurgeComm(handle, PURGE_RXCLEAR | PURGE_TXCLEAR);
            }
     
            /// <summary>
            /// Opens and initializes the serial connection.
            /// </summary>
            /// <returns>Whether or not the operation succeeded</returns>
            public bool Open()
            {
                handle = CreateFile(this.portName, FileAccess.ReadWrite, FileShare.None,
                    IntPtr.Zero, FileMode.Open, 0, IntPtr.Zero);
                if (handle == IntPtr.Zero) return false;
     
                if (ConfigureSerialPort()) return true;
                else
                {
                    Dispose();
                    return false;
                }
            }
     
            /// <summary>
            /// Reads any bytes that have been received and writes them to the specified array.
            /// </summary>
            /// <param>The array to write the read data to</param>
            /// <returns>The number of bytes read (-1 if error)</returns>
            public int Read(byte[] data)
            {
                FailIfNotConnected();
                if (data == null) return 0;
     
                int bytesRead;
                if (ReadFile(handle, data, data.Length, out bytesRead, 0))
                    return bytesRead;
                return -1;
            }
     
            /// <summary>
            /// Reads any data that has been received as a string.
            /// </summary>
            /// <param>The maximum number of bytes to read</param>
            /// <returns>The data received (null if no data)</returns>
            public string ReadString(int maxBytesToRead)
            {
                if (maxBytesToRead < 1) throw new ArgumentOutOfRangeException("maxBytesToRead");
     
                byte[] bytes = new byte[maxBytesToRead];
                int numBytes = Read(bytes);
                string data = ASCIIEncoding.ASCII.GetString(bytes, 0, numBytes);
                return data;
            }
     
            /// <summary>
            /// Transmits the specified array of bytes.
            /// </summary>
            /// <param>The bytes to write</param>
            /// <returns>The number of bytes written (-1 if error)</returns>
            public int Write(byte[] data)
            {
                FailIfNotConnected();
                if (data == null) return 0;
     
                int bytesWritten;
                if (WriteFile(handle, data, data.Length, out bytesWritten, 0))
                    return bytesWritten;
                return -1;
            }
     
            /// <summary>
            /// Transmits the specified string.
            /// </summary>
            /// <param>The string to write</param>
            /// <returns>The number of bytes written (-1 if error)</returns>
            public int Write(string data)
            {
                FailIfNotConnected();
     
                // convert the string to bytes
                byte[] bytes;
                if (data == null) bytes = null;
                else bytes = ASCIIEncoding.ASCII.GetBytes(data);
                return Write(bytes);
            }
     
            /// <summary>
            /// Transmits the specified string and appends the carriage return to the end
            /// if it does not exist.
            /// </summary>
            /// <remarks>
            /// Note that the string must end in '\r\n' before any serial device will interpret the data
            /// sent. For ease of programmability, this method should be used instead of Write() when you
            /// want to automatically execute the specified command string.
            /// </remarks>
            /// <param>The string to write</param>
            /// <returns>The number of bytes written (-1 if error)</returns>
            public int WriteLine(string data)
            {
                if (data != null && !data.EndsWith("\r\n"))
                    data += "\r\n";
                return Write(data);
            }
     
            #region Private Helpers
     
            /// <summary>
            /// Configures the serial device based on the connection parameters pased in by the user.
            /// </summary>
            /// <returns>Whether or not the operation succeeded</returns>
            private bool ConfigureSerialPort()
            {
                DCB serialConfig = new DCB();
                if (GetCommState(handle, ref serialConfig))
                {
                    // setup the DCB struct with the serial settings we need
                    serialConfig.BaudRate = (uint)this.baudRate;
                    serialConfig.ByteSize = this.byteSize;
                    serialConfig.fBinary = 1; // must be true
                    serialConfig.fDtrControl = 1; // DTR_CONTROL_ENABLE "Enables the DTR line when the device is opened and leaves it on."
                    serialConfig.fAbortOnError = 0; // false
                    serialConfig.fTXContinueOnXoff = 0; // false
     
                    serialConfig.fParity = 1; // true so that the Parity member is looked at
                    switch (this.parity)
                    {
                        case Parity.Even:
                            serialConfig.Parity = 2;
                            break;
                        case Parity.Mark:
                            serialConfig.Parity = 3;
                            break;
                        case Parity.Odd:
                            serialConfig.Parity = 1;
                            break;
                        case Parity.Space:
                            serialConfig.Parity = 4;
                            break;
                        case Parity.None:
                        default:
                            serialConfig.Parity = 0;
                            break;
                    }
                    switch (this.stopBits)
                    {
                        case StopBits.One:
                            serialConfig.StopBits = 0;
                            break;
                        case StopBits.OnePointFive:
                            serialConfig.StopBits = 1;
                            break;
                        case StopBits.Two:
                            serialConfig.StopBits = 2;
                            break;
                        case StopBits.None:
                        default:
                            throw new ArgumentException("stopBits cannot be StopBits.None");
                    }
     
                    if (SetCommState(handle, ref serialConfig))
                    {
                        // set the serial connection timeouts
                        COMMTIMEOUTS timeouts = new COMMTIMEOUTS();
                        timeouts.ReadIntervalTimeout = 1;
                        timeouts.ReadTotalTimeoutMultiplier = 0;
                        timeouts.ReadTotalTimeoutConstant = 0;
                        timeouts.WriteTotalTimeoutMultiplier = 0;
                        timeouts.WriteTotalTimeoutConstant = 0;
                        if (SetCommTimeouts(handle, ref timeouts))
                            return true;
                        else return false;
                    }
                    else return false;
                }
                else return false;
            }
     
            /// <summary>
            /// Helper that throws a InvalidOperationException if we don't have a serial connection.
            /// </summary>
            private void FailIfNotConnected()
            {
                if (handle == IntPtr.Zero)
                    throw new InvalidOperationException("You must be connected to the serial port before performing this operation.");
            }
     
            #endregion
     
            #region Native Helpers
     
            // Used to get a handle to the serial port so that we can read/write to it.
            [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
            static extern IntPtr CreateFile(string fileName,
               [MarshalAs(UnmanagedType.U4)] FileAccess fileAccess,
               [MarshalAs(UnmanagedType.U4)] FileShare fileShare,
               IntPtr securityAttributes,
               [MarshalAs(UnmanagedType.U4)] FileMode creationDisposition,
               int flags,
               IntPtr template);
     
            // Used to close the handle to the serial port.
            [DllImport("kernel32.dll", SetLastError = true)]
            static extern bool CloseHandle(IntPtr hObject);
     
            /// <summary>
            /// Defines the control setting for a serial communications device.
            /// </summary>
            [StructLayout(LayoutKind.Sequential)]
            struct DCB
            {
                public int DCBlength;
                public uint BaudRate;
                public uint Flags;
                public ushort wReserved;
                public ushort XonLim;
                public ushort XoffLim;
                public byte ByteSize;
                public byte Parity;
                public byte StopBits;
                public sbyte XonChar;
                public sbyte XoffChar;
                public sbyte ErrorChar;
                public sbyte EofChar;
                public sbyte EvtChar;
                public ushort wReserved1;
                public uint fBinary;
                public uint fParity;
                public uint fOutxCtsFlow;
                public uint fOutxDsrFlow;
                public uint fDtrControl;
                public uint fDsrSensitivity;
                public uint fTXContinueOnXoff;
                public uint fOutX;
                public uint fInX;
                public uint fErrorChar;
                public uint fNull;
                public uint fRtsControl;
                public uint fAbortOnError;
            }
     
            // Used to get the state of the serial port so that we can configure it.
            [DllImport("kernel32.dll")]
            static extern bool GetCommState(IntPtr hFile, ref DCB lpDCB);
     
            // Used to configure the serial port.
            [DllImport("kernel32.dll")]
            static extern bool SetCommState(IntPtr hFile, [In] ref DCB lpDCB);
     
            /// <summary>
            /// Contains the time-out parameters for a communications device.
            /// </summary>
            [StructLayout(LayoutKind.Sequential)]
            struct COMMTIMEOUTS
            {
                public uint ReadIntervalTimeout;
                public uint ReadTotalTimeoutMultiplier;
                public uint ReadTotalTimeoutConstant;
                public uint WriteTotalTimeoutMultiplier;
                public uint WriteTotalTimeoutConstant;
            }
     
            // Used to set the connection timeouts on our serial connection.
            [DllImport("kernel32.dll", SetLastError = true)]
            static extern bool SetCommTimeouts(IntPtr hFile, ref COMMTIMEOUTS lpCommTimeouts);
     
            // Used to read bytes from the serial connection.
            [DllImport("kernel32.dll")]
            static extern bool ReadFile(IntPtr hFile, byte[] lpBuffer,
               int nNumberOfBytesToRead, out int lpNumberOfBytesRead, int lpOverlapped);
     
            // Used to write bytes to the serial connection.
            [DllImport("kernel32.dll", SetLastError = true)]
            static extern bool WriteFile(IntPtr hFile, byte[] lpBuffer,
                int nNumberOfBytesToWrite, out int lpNumberOfBytesWritten, int lpOverlapped);
     
            // Used to flush the I/O buffers.
            [DllImport("kernel32.dll", SetLastError = true)]
            static extern bool PurgeComm(IntPtr hFile, int dwFlags);
     
            #endregion
        }