Introduction

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

Using the code

Create and open 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 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 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 flags NOPARITY, ODDPARITY, EVENPARITY, MARKPARITY, and SPACEPARITY.

To change the number of stop bits, the choice is:

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

Preparing data to send

Because of the special nature of the Ruby language, 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 it 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 items extracted from the binary string according to the formatter parameter.

For example, let's see how to 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 one 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. ‘i’ is the signed integer number, and ‘a12’ is the string of 12 bytes. As a result, the binaryString contains 20 bytes: 4 bytes for each integer (32 bit Windows) and 12 bytes of characters.

The binaryString contains data in the format ready to send.

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.

Receiving data

There are two methods in the class allowing 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 it was available in the input buffer of the serial port but not more than specified.

The second method readUntil blocks execution of the program until it reads the specified number of bytes. It will return with less bytes than specified if the serial port is closed.

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 the received data. Here are some 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 a 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)

Missing features

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

  • Implementation of the IO interface,
  • Comm timeouts configuration (see the SetCommTimeouts function of the MS Windows API),
  • And access to the modem pins (DCD, DTR, etc.).
推荐.NET配套的通用数据层ORM框架:CYQ.Data 通用数据层框架
新浪微博粉丝精灵,刷粉丝、刷评论、刷转发、企业商家微博营销必备工具"