Q330 Serial Port Communication

The Quanterra Q330 is a seismic data logger originally manufactured by Quanterra Inc., now a subsidiary of Kinemetrics Inc. The Q330 saw widespread adoption in the early 2000s. While it is not the newest Kinemetrics product anymore, there are still many Q330s in service. The Q330 is the primary data logger used in the EarthScope USArray Transportable Array (TA), and it is one of two primary data loggers available through the IRIS PASSCAL Instrument Center.

Between 2007 and 2009 several Q330s were installed in the High Lava Plains (HLP) seismic experiment. It was during that experiment that I got to know those data loggers. During the same time I was experimenting with in-house software for data conversion and data logger control. While I was mainly focusing on the competing REF TEK RT130 data logger, this activity got me interested in the Q330’s communication protocol as well. So I dug into the Q330 documentation and did some experiments while having brief access to a borrowed Q330. Programming serious control software for the Q330 would have taken more time than I was willing to afford, so eventually all my observations went into a text file, which got buried deep down in my hard drive. Recently, I found those notes again, and thought that they might be turned into a useful blog post. Even if you are not at all interested in the Q330, the following overview might still serve as a neat example on how the protocols of different network layers interact in the context of a somewhat unusual communication channel.

The Q330 data logger can be controlled remotely via Ethernet using a custom protocol termed QDP, which is transported via IP/UDP. Alternatively, a computer or a Palm handheld (the old style) may be connected to a Q330 locally through one of two serial ports: one wired and the other wireless via IrDA. Interestingly, these serial communication options use the exact same QDP protocol over IP/UDP as in the case of an Ethernet connection. This is made possible by framing the IP datagrams, which are carrying the UDP and ultimately QDP packets, using the SLIP protocol, so that they can be transmitted over a continuous serial byte stream. Let’s have a more detailed look at all the involved network layers and protocols.

Physical layer: RS232

When connecting to a Q330’s CONSOLE port, one is establishing a standard RS232 serial link, although the serial lines are wired to not-so-standard pins on a 10-pin MIL-spec connector. The Q330 does support hardware handshaking, but in the standard setup this feature is disabled. This allows the use of a minimal cable with just three wires connected: TXD on pin G, RXD on pin H, and GND on pin E. Note, that the Q330 uses the exact same MIL-spec connector as the competing REF TEK RT130 data logger; however, the two systems use different, incompatible pin assignments.

The RS232 communication parameters can be set to different values, but the following are standard:

  • Bit rate: 19200 BPS
  • Format: 8N1 (8 bits, no parity, one stop bit — standard values)
  • No hardware handshaking (three wire connection); but note that hardware handshaking (using CTS, RTS, DTS, and DTR) can be enabled.

Instead of using the CONSOLE port, one can connect wirelessly to the Q330 via IrDA. I have used the IrDA port only once (on a Q330 with a damaged CONSOLE port) and it was a major pain: one needs to hold the Palm handheld within roughly 10 cm of the Q330’s IrDA port and shade the area from the sun to establish a halfway reliable connection. I will not go into technical details of IrDA here, since I know not much about it. Just note, that at the device driver level, an IrDA connection looks basically the same as a regular RS232 connection, meaning that all the following discussion applies equally to both options.

Data Link layer: SLIP

At the device driver level, a serial transmission via RS232 resembles just a continuos stream of byte-sized characters. How can one send variable length IP datagrams over such a continuous byte stream? Two popular packet framing protocols have been developed to deal with this issue: Serial Line IP (SLIP) and Point to Point Protocol (PPP). While PPP is more advanced, for the purpose of directly connecting two nodes via RS232, the older SLIP is fully sufficient, and much simpler to implement. The details of SLIP are well documented in its original RFC1055, which also includes some very useful example C code.

The idea of SLIP is to basically send all characters within a datagram as-is, but add a special END character at the end of each datagram to provide a very simple mechanism of packet framing. The SLIP END character was arbitrarily chosen to have the octal value of \300. But there is one problem: what if any data byte within the packet happens to have the same value as the END character? To handle this, any such character must be replaced by a special two-character escape sequence. Of course, this introduces another special character, ESC, which may need to be replaced with another escape sequence as well. So we and up with two possible escape sequences, which are labeled ESC ESC_END, and ESC ESC_ESC. Here is a list of all involved special characters:

  • END, octal \300 — frames a packet
  • ESC, octal \333 — to form ESC ESC_END, and ESC ESC_ESC sequences
  • ESC_END, octal \334 — second part of ESC ESC_END sequence
  • ESC_ESC, octal \335 — second part of ESC ESC_ESC sequence

Note, that even though the SLIP special characters END and ESC share their symbolic names with the END and ESC characters in the ASCII character set, they have different octal values!

While not being a strict requirement of SLIP, it is common to prepare for every new datagram by first sending an END character. This allows the receiver to flush any garbage from its input buffer before starting to assemble a new datagram. It is not uncommon to encounter several END characters in a group. They may be interpreted as a sequence of zero sized datagrams, which should be dropped to avoid spending time to try to parse them. As a further optimization, any datagram of less than 20 bytes length (the minimum length of an IPv4 header) should be dropped early on as well. This will effectively strip out any spurious bytes, which may be introduced from connecting or disconnecting a serial cable while the port is open.

One downside of SLIP is that it does not provide any means of error checking. So aside from dropping datagrams below the minimum IP header size, all further error checking must be performed at the next higher network and transport layers.

Network layer: IP Header

QDP is transmitted via UDP, which in turn is transmitted via IP, so the outermost encapsulation layer inside a SLIP frame will be the IP datagram, which always starts with an IP header. For QDP packets, this will be followed by an UDP header, which again will be followed by a QDP header. But let’s just look at the IP header at this time. Here it is as a heavily commented C struct derived from netinet/ip.h:

struct ip {
  unsigned int   ip_hl:4;   /* Header length in 32-bit octets. Default is 5, indicating 20 header bytes. */
  unsigned int   ip_v:4;    /* IP version. 4 stands for IPv4. */
  uint8_t        ip_tos;    /* Type of Service. Defaults to 0. */
  uint16_t       ip_len;    /* Total length of IP datagram. Includes header and payload. */
  uint16_t       ip_id;     /* ID sequence number. To reassemble fragmented datagrams. */
  uint16_t       ip_off;    /* Fragment offset. To reassemble fragmented datagrams. */
  uint8_t        ip_ttl;    /* Time to live. Number of allowed hops left, to be decreased by routers. */
  uint8_t        ip_p;      /* Transport layer protocol. 17 stands for UDP. */
  uint16_t       ip_sum;    /* IP header checksum. */
  struct in_addr ip_src;    /* Source IP address. */
  struct in_addr ip_dst;    /* Destination IP address. */
};

The Q330 supports only IPv4, and all QDP packets are transported via UDP. Assuming furthermore, that no datagrams will be fragmented, the following consistency checks may be applied:

ip_valid = ip_hl == 5 && ip_v == 4 && ip_len == slip_len && ip_off == 0 && ip_p == 17
        && ip_sum = crc32(ip, ip_len);

If any of these tests fail, it is probably not worthwhile to examine the datagram any further. Note, that for our “dummy” network connection via SLIP there is really no need of any addressing scheme.

Transport layer: UDP

The UDP header does not add a lot of information to the IP header. Its main purpose is to provide source and destination port numbers to address specific UDP sockets. The definition of UDP is, as the definition of its sister protocol TCP, tightly coupled to the definition of IP. This is why many people consider the network and transport layers as practically inseparable. Let’s look at the UDP packet header to spot the problem:

struct udphdr {
  uint16_t    uh_sport;   /* Source port. */
  uint16_t    uh_dport;   /* Destination port. */
  uint16_t    uh_ulen;    /* UDP length. Calculated over entire datagram. */
  uint16_t    uh_sum;     /* UDP packet checksum. Calculated over entire datagram using a pseudo IP header. */
};

The calculation of the UDP checksum uses a “pseudo IP header”, a simplified IP header made up from information only present in the real IP header. This breaks the concept of encapsulation, since correct handling of the UDP packet requires information from the IP header. What this means for a program that analyses data coming from a Q330 is that the IP and UDP packet headers should be checked and handled by the same subroutine, as if they belonged to a shared network layer.

Application layer: QDP

The payload of the UDP packets finally contains the Q330 specific application layer data encoded as QDP packets. I will not delve into the details of the QDP protocol here. So I will restrict myself to the one component, which is common among all QDP packets: the QDP packet header.

struct qdphdr {
  uint32_t   crc;      /* Packet Checksum. CRC32 including header (without CRC field) and payload. */
  uint8_t    command;  /* QDP command code. Indicates type of payload data. */
  uint8_t    version;  /* QDP version. */
  uint16_t   length;   /* Packet length. */
  uint16_t   seq_nr;   /* Sequence number. Auto-incrementing number to spot missing or duplicate packets. */
  uint16_t   ack_nr;   /* Acknowledge number. To send ACK signals for previously received packets. */
};

Interesting here is, that QDP includes sequence and acknowledgement numbers. This allows sequences of QDP packets to be sent and received in a connection oriented manner. In a sense, the designers of QDP chose to re-implement a part of TCP over the connectionless UDP. I can only guess that their motivation was to allow both, connectionless and connection-oriented behavior, depending on whether simple command or status messages are exchanged, or more complex multi-packet data downloads are performed.

To keep this post to a reasonable length, I will stop here with my analysis of the Q330 communication protocol. Digging deeper into QDP would anyhow surpass my own knowledge of the matter. I would also risk to give away information from the Q330 documentation, which might not be intended for the general public. I hope, that going just so far might already help someone working with the Q330 documentation to get a jumpstart on understanding QDP. For everyone else, hopefully this was a neat example of how the different Internet model layers stack on top of each other. Writing it up certainly helped me to get a better grip on this matter.

This entry was posted in ITE 221, Q330. Bookmark the permalink.

2 Responses to Q330 Serial Port Communication

  1. Hi, studying the Q330 I found the following in a telemetry adjustment parameter guide:

    “…uysing 5380 bits / packet…”
    I can’t figure how can get to that bits / packet rate in the Quanterra

    Any help?
    thanks

  2. Hi, studying the Q330 I found the following in a telemetry adjustment parameter guide:

    “…uysing 5380 bits / packet…”
    I can’t figure how can get to that bits / packet rate in the Quanterra

    Any help?
    thanks

Comments are closed.