Tech Off Thread

7 posts

Forum Read Only

This forum has been made read only by the site admins. No new threads or comments can be added.

Stumbling on Basic Serial Port IO

Back to Forum: Tech Off
  • User profile image
    ploe

    I wrote a quick C# serial port wrapper class that uses the native APIs to communicate over serial (CreateFile, ReadFile, SetCommState, etc). Reading various posts online, it appears that alot of people have had problems with the .Net SerialPort class.

     

    Anyway, I feel like I'm missing some fundamental understanding of how serial communication works. When I send a command using WriteFile(), ReadFile() returns the exact command I sent instead of the response to my command. If I send "AT", for example, my read returns "AT". I feel like I'm not truly reading the devices response, but just what I wrote. Sad

     

    So I thought I'd give the .Net SerialPort class a trying. I found this sample (http://msdn.microsoft.com/en-us/library/y2sxhat8.aspx), but when I run this it too just returns exactly what I sent instead of the device's response.

     

    I know the device works because I can connect with it via Putty and I get the responses I expect. Can anyone point me in the right direction?

  • User profile image
    ManipUni

    What is the return code you get when you attempt to open the COM port? Also how long are you sleeping for after sending the command?

  • User profile image
    ScanIAm

    The modem is in echo mode (which is good) but it won't respond until you send it a command and a carriage return character 0x0A and possibly a linefeed 0x0D.

     

    Also, just sending AT won't make it actually do anything.  you need to tell it to do something.  If you are just looing for an 'OK' response (or possible a '0'), then send 'ATZ' following by a carriage return character.

     

     

  • User profile image
    ManipUni

    ScanIAm said:

    The modem is in echo mode (which is good) but it won't respond until you send it a command and a carriage return character 0x0A and possibly a linefeed 0x0D.

     

    Also, just sending AT won't make it actually do anything.  you need to tell it to do something.  If you are just looing for an 'OK' response (or possible a '0'), then send 'ATZ' following by a carriage return character.

     

     

    It is modem... OH, sorry, I misunderstood. I thought you were attempting PC to PC serial communications.

  • User profile image
    ScanIAm

    ManipUni said:
    ScanIAm said:
    *snip*

    It is modem... OH, sorry, I misunderstood. I thought you were attempting PC to PC serial communications.

    ~7 years modem support in the early 90s Smiley

  • User profile image
    ploe

    ScanIAm said:
    ManipUni said:
    *snip*

    ~7 years modem support in the early 90s Smiley

    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.

  • 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
        }
    

Conversation locked

This conversation has been locked by the site admins. No new comments can be made.