Click here to Skip to main content
Click here to Skip to main content

Win32 Serial Port for Ruby

, 15 Mar 2011
Rate this:
Please Sign up or sign in to vote.
The Win32 serial port class for Ruby.

Introduction

Win32SerialPort::SerialPort is a simple class for Ruby which helps to access a serial port in Windows. This class uses the standard Win32 API and does not require any external C/C++ libraries.

Using the code

This code was tested on Windows XP, Ruby 1.8.6, and Win32API gem 1.4.8.

1. Create and open a serial port

The serial port class is stored in the win32serial module and is encapsulated in the Win32SerialPort namespace. Use require to attach the module to your script.

require "win32serial"

Create an instance of the serial port object using the new function as follows:

serial = Win32SerialPort::SerialPort.new

The next step is to open the serial port and make it ready to use. For example, you might want to open COM1 in the 115200,8,n,1 mode without a flow control mode (baudrate: 115200, 8 data bits, no parity, and 1 stop bit). The following example does the job:

# Open COM1 serial port in 115200,8,n,1 mode without flow control
return 0 if false == serial.open(
    "COM1",                        # port name
    115200,                        # baudrate
    Win32SerialPort::FLOW_NONE,    # no flow control
    8,                             # 8 data bits
    Win32SerialPort::NOPARITY,     # no parity bits
    Win32SerialPort::ONESTOPBIT)   # one stop bit

The parameters passed to the function are forwarded as they are to the Windows library, and if the open fails, it is because of Windows limitations. The open function returns false if it fails to open the serial port and true when the serial port is ready to use.

To turn on the hardware flow control, use the FLOW_HARDWARE switch instead of FLOW_NONE.

To switch the parity, use the flags NOPARITY, ODDPARITY, EVENPARITY, MARKPARITY, and SPACEPARITY.

To change the number of stop bits, the choices are:

  • ONESTOPBIT – one stop bit
  • ONE5STOPBITS  - 1.5 stop bits
  • TWOSTOPBITS – two stop bits

The number of data bits must be 5 to 8 bits.

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.

Use the close function to close the serial port.

There is an option to configure serial port timeouts. There are five timeouts defined in the COMMTIMEOUTS structure in the Windows API: ReadIntervalTimeout, ReadTotalTimeoutMultiplier, ReadTotalTimeoutConstant, WriteTotalTimeoutMultiplier, and WriteTotalTimeoutConstant.

The following example sets the first three read timeouts to 0 and the last two write timeouts to 100ms and 1000ms. These timeouts are passed to the setCommTimeouts function as a table of 5 numbers.

# Configure serial port read/write timeouts
timeouts = [0,0,0,100,1000]
result = serial.setCommTimeouts(timeouts)
print "\nSetCommTimeouts result: " + result.to_s + "\n\n"

2. Preparing data to send

The special nature of the Ruby language causes that the data to be sent has to be specially prepared. This paragraph is more about how to prepare data than about using the serial port class itself. If you are familiar with the Array::pack and String::unpack methods, you can skip this paragraph.

The Array in Ruby is an object and cannot be interpreted as a stream of bytes as it is required when parameters are passed to the Windows kernel. It means that data to be sent to a serial port must be prepared before.

There are two functions in the Ruby library which helps to convert data from an array to a stream of bytes and the stream of bytes back to the array format. The first one is useful when transmitting and the second when receiving bytes.

Each item of an Array class instance may have a different type and that means that a different size too. The Array class has the pack method which returns the items of the array as a string of bytes. Each item of the array takes the required number of bytes in a string and is placed one after another every item. The pack method does not know how to interpret its items. So the method takes a parameter called formatter which describes how to format the items. For example, ‘i’ means a signed integer number, ‘f’ means a floating point number, and ‘a’ is a string. All formatters are described in the pack method documentation.

When data is received from the serial port, it is represented as a string of bytes. Each application expecting to receive data from the serial port also knows how to parse the received bytes and knows how to extract information from that string. The String class has the unpack method which takes the formatter parameter (with the same switches as the pack method mentioned earlier) describing how to interpret the received bytes. The method returns an instance of the Array class containing the items extracted from the binary string according to the formatter parameter.

For example, prepare a binary string of two integers and a characters string. First of all, create an array:

toSend = [4,7,"Hello World!"]

The array toSend has three items. Two integers (4, 7) and a string (Hello World!).

The array must be converted to a binary string as follows:

binaryString = toSend.pack("iia12")

“iia12” is the formatter parameter which tells the pack method how to interpret the items stored in the toSend array. The ‘i’ is the signed integer number and ‘a12’ is the string of 12 bytes. As a result, binaryString contains 20 bytes: 4 bytes for each integer (32 bit Windows) and 12 bytes of characters.

binaryString contains data in the format ready to send.

3. Sending data

The write method takes only one parameter. The parameter is a string. Here are a few examples:

  • Send a simple string:
  • # send simple string
    written = serial.write(“Hello World!”)
  • Send a number as a string:
  • # one character “7”
    i = 7
    written = serial.write(i.to_s)
     
    # two characters: 7 and 6
    i = 76
    Written = serial.write(i.to_s)
  • Send an array of bytes (see the ‘Preparing data to send’ paragraph for an explanation):
  • # an array of items
    toSend = [4,7,"Hello World!"]
     
    # the array of items converted to a binary string
    binaryString = toSend.pack("iia12")
     
    # send the binary string
    written = serial.write(binaryString)
     
    #
    # Test if data has been sent
    if 0 < written  
      print "Data has been successfully sent\n"
    else
      print "Could not send data\n"
    end

The write method returns the number of bytes that have been sent.

4. Receiving data

There are two methods in the class allowing the reading of received bytes. The read method tries to read the number of bytes specified in the input parameter. It returns immediately with as many bytes as was available in the input buffer of the serial port but not more than specified.

The second method readUntil blocks execution of a program until it reads the specified number of bytes. It will return with less bytes than specified if the serial port is closed. If serial port timeouts are set not to wait for data, readUntil will return immediately with the bytes available to read.

Both functions return a binary string containing the received bytes. See the ‘Preparing data to send’ paragraph for an explanation of how to parse/interpret received data. Examples:

  • Read all available (received) bytes:
  • # Reads as many bytes as it is stored in
    # the input buffer of the serial port.
    binString = serial.read
    if binString.length > 0 
      print "Received data: " + binString + "\n"
    else
      print "Nothing received\n"
    end
  • Read no more than 10 bytes:
  • # Returns 10 or less bytes of data
    binString = serial.read(10)
    if binString.length > 0 
      print "Received " + binString.length.to_s + " bytes\n"
    else 
      print "Nothing received\n"
    end
  • Read no less than 10 bytes:
  • # blocks until 10 bytes is available
    binString = serial.readUntil(10)
     
    # test the length in case if the serial port has been closed
    if binString.length > 0 
      print "Received " + binString.length.to_s + " bytes\n"
    else 
      print "Nothing received\n"
    end

    There is the bytesToRead attribute in the class which returns the number of bytes available to read from the receive buffer of the serial port.

    bytesAvailable = serial.bytesToRead
    print "\nBytes available to read: " + bytesAvailable.to_s + "\n"
     
    binString = serial.readUntil(bytesAvailable)

5. Missing features

A quick look at the System.IO.Ports.SerialPort class from the Microsoft .NET library, for example, is enough to see the lack of a few interesting features of the class described here. Probably the most important would be:

  • implementation of the IO interface,
  • and access to the modem pins (DCD, DTR, etc.)

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

No Biography provided

Comments and Discussions

 
QuestionSerial on USB PinmemberPiotr Grygorczuk7-Sep-11 22:02 
QuestionWriting to port works, but reading doesn't Pinmembertbodine8810-Aug-11 9:44 
AnswerRe: Writing to port works, but reading doesn't PinmemberPiotr Grygorczuk7-Sep-11 21:44 
GeneralRe: Writing to port works, but reading doesn't PinmemberMember 824897119-Sep-11 22:21 
GeneralRe: Writing to port works, but reading doesn't PinmemberPiotr Grygorczuk4-Oct-11 1:55 
GeneralRe: Writing to port works, but reading doesn't Pinmemberml1234528-Jul-12 5:34 
Generalthanks PinmemberLuffy Protgas14-Mar-11 18:33 
GeneralWinXP Pinmemberalex26330-Jan-11 9:34 
GeneralRe: WinXP PinmemberPiotr Grygorczuk22-Feb-11 14:06 
GeneralCiekawy :) Pinmembervictor 201019-Feb-10 11:16 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web04 | 2.8.140721.1 | Last Updated 15 Mar 2011
Article Copyright 2009 by Piotr Grygorczuk
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid