Geiger counter data logging in C#

I got my hands on a Geiger counter the other day. It’s a GMC-300E, they’re affordable, and the build quality is decent.

The device has an unlocked serial interface via USB, and the manufacturer has published the protocol documentation. This allows you to download the logged data and read life data from the Geiger-Müller tube, and with the 320 model also from the temperature sensor and gyroscope.

This is a quick guide on how to access the GMC-300E serial interface in C#.

First, you’ll need a device driver for the USB-to-Serial chip in the device. You can download them from the manufacturer’s website. If those drivers don’t work (as they didn’t for me), google for “usb serial CH340 driver” (that’s the chip for the serial interface). Power on the device, then connect it via USB. If everything works correctly, you should see a new COM(x) entry in your Control Panel->Device Manager->Ports list. Select Properties from the rightclick menu and set the Port Settings:


According to the manufacturer docs, the baud rate (“bits per second”) should be set to 115200, but other values seem to work too. You can download the data viewer tool (“GQ Geiger Counter Data Viewer”) test if you’ve configured the port correctly. Select “auto detect” from the menu and tick the “realtime monitoring” checkbox.


Let’s start programming! The C#/.Net standard library already comes with all the required classes you’ll need to interface a serial port (they’re in System.IO.Ports). Sending and receiving data works similar to a network connection. You open a port and register an event handler for incoming data. Now you can send some commands to the device. The GETVER command is a simple way to check if everything works correctly.

port = new SerialPort("COM9", 57600, Parity.None, 8, StopBits.One);
port.DataReceived += new SerialDataReceivedEventHandler(port_DataReceived);
port.WriteLine("<GETVER>>"); // get the device version

When the device is sending data back, you can read it in the “data received” event handler.

private static void port_DataReceived(object sender, SerialDataReceivedEventArgs e)
var buf = new byte[port.BytesToRead];
port.Read(buf, 0, buf.Length);


That’s the easy part. The data logging itself requires some bit fiddling.

There’s two ways to get the CPM data from the device. You can either send the <GETCPM>> command, and it will return the current CPM value. There’s also the <HEARTBEAT1>> command, which you send once to the device, and it will continuously stream the CPM data, one packet per second.

Please note! The GETCPM command returns CPM (“counts per minute”) data – the HEARTBEAT command, however, will send CPS, “counts per second“. Do not be surprised if the packets you receive contain mostly 0’s and 1’s – with normal background radiation, you’ll rarely encounter more than 2 counts per second. Add the last 60 heartbeats to get the current CPM value.

The heartbeat packets contain a single unsigned Int16 (ushort) value, encoded in two bytes. The first byte is MSB, the second LSB. You’ll have to reverse the byte order first before you can convert them. Also, the last two bits are system reserved, so just set them to zero.

var bits = new System.Collections.BitArray(buf);
bits[14] = false;
bits[15] = false; // "The highest bit 15 and bit 14 are reserved data bits."
bits.CopyTo(buf, 0);
var outint = (ushort)BitConverter.ToUInt16(buf, 0); // the CPS value as unsigned short

Collect the values in a list. The total value of the last 60 entries in the list is the current CPM value.


That’s all! Now you can log the data to a file or wrap a little HTTP server around it so you can access it from a network.

You can download the full source code here or from pastebin. The complete protocol documentation for the GMC-300E Geiger counter is here.