Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C

Radiation Counter on Windows RT

4.67/5 (6 votes)
22 Jan 2014Ms-PL17 min read 33.4K   1.5K  
First of a kind radiation counter directly connected to ARM processor on Windows RT

Introduction

This article demonstrates the first Windows RT based radiation counter, which is directly connected to ARM processor (i.e may be built into Windows RT tablet and not using any transport layer, such as not using any of I2C, USB, SPI or serial). Both Windows RT device driver and sample test applications are described.

Some time in the past I had a research project where prototype Windows RT platform (similar to Raspberry Pi) was under investigation regarding how fast it can handle GPIO interrupts. Later on I have decided to give some practical use to the results of that research project and here came a radiation counter for Windows RT project.

Important Note

This is experimental hardware and software mix engineering project, which eventually may deal with measuring radioactivity. In no case this project shall be used for real life application.

Background

You have certainly heard about radiation detectors, they are typically stand-alone microcontroller based small portable devices with battery power, typically capable of measuring β and γ ionizing radiation with a primitive digital display and maybe also with some communication to PC to send results to for later processing, nice graphs and other ways to share its data.

Usually radiation detectors are communicating with PC or smart phone over some transport layer, which may be implemented as serial, USB, I2C, SPI or other communication method. PC or smart phone then runs some software application such as Windows Radiation Logger, MacOS Geiger Bot and so on to get data from radiation detector. Typically it means CPM value from radiation detector firmware is sent to PC or smartphone every several seconds or so.

Unlike those, my prototype product does not use any transport layer between Geiger counter hardware and Windows RT – no serial, no USB, no I2C. It is a simple direct wire connection from load resistor of Geiger counter to GPIO pin of ARM processor. Therefore, every single particle detected by Geiger counter generates an interrupt on ARM processor in real time. Since Windows RT runs on the same ARM processor, we now got a possibility to have radiation counter built into Windows RT platform directly.

Here is a hardware diagram showing components layout. A Windows RT development tablet platform was used to carry out this project. The Geiger counter gets its high voltage from tiny power supply fed by the same tablet Li-ion battery. A LND712 Geiger tube was used as a test, it is too big to fit inside of retail sized tablet, but using small form factor Geiger tube one could have it built into retail Windows RT tablet case.

 Image 1

To use existing hardware I took advantage of partially populated Geiger DIY kit from brhogan, leaving out microcontroller and using only HV power supply part of the kit.

So we have got all hardware in place now. What is left is the software to write. Luckily Windows RT development tablet platform made available to me was unlocked so I can achieve what I need without hurdles both at device driver and at application levels.

We need three parts – first new counter ACPI resources, then a Windows RT device driver to service GPIO interrupts, and lastly Windows RT application using our driver to test radiation counter.

Part 1 Windows RT radiation counter ACPI resources 

First we need to tell Windows RT that we have new hardware resource, so it can then let us load Windows device driver for it. I have done it through ACPI table, by adding this section to system ACPI table. Our radiation counter resource is really simple – we only need one resource, interrupt that is. To tell ACPI which GPIO pin is ours we set it explicitly (GPIO pin 142 in my example). We also specify that it is must be active-high, i.e. zero Volts is no interrupt, and anything higher than 1.8 Volts is an interrupt. To complete new device ACPI section properly some device names have to be supplied per ACPI rules, so our radiation counter device driver should appear as a device with a name GMCT0001.

C++
Device(GMC1)
{
        Name(_ADR, 0)
        Name(_HID, "GMCT0001")
        Name(_CID, "GMCT0001")
        Name(_UID, 1)
 
        Method(_CRS, 0x0, NotSerialized)
        {
               Name(RBUF, ResourceTemplate()
               {
// GPIO Interrupt Resource
                       // Geiger counter load resistor to GPIO 142
                       GpioInt(Edge, ActiveHigh, Shared, PullDown, 0, "\\_SB.GPI2", 0, ResourceConsumer,, RawDataBuffer() {0x15}) {142} 
               })
               Return (RBUF)
        }
 
        Method(_STA, 0x0, NotSerialized)
        {
               Return(0xf)
        }
}  // end of Device (GMC1) section

To make the changes described above to take effect one needs to insert this code fragment to platform ACPI table, rebuild ACPI binary, and then update platform tablet firmware with your new ACPI. Then reboot Windows RT to verify that you now see new unknown device in Windows RT device manager. If we want to ensure that it is really our radiation counter device, then inspect this unknown device details in device manager, the interrupt number should match ours as well as the ACPI device name - GMCT0001.

Part 2 Windows RT radiation counter KMDF device driver

Developing Windows RT device drivers may be tricky at times, but this one is easy.

First our radiation counter Windows device driver .inf file should have resource description matching what we have previously put into ACPI section. Below is a snippet from .inf file where a comment reminds to have matching ACPI device name

[Standard.NT$ARCH$]
%GM_counter_Driver1.DeviceDesc%=GM_counter_Driver1_Device, ACPI\GMCT0001 ; in ACPI table described as GMCT0001

In terms of functionality all we need to do in our Windows RT device driver is react to each and every interrupt generated by Geiger counter pulse on GPIO 142, and service such interrupt. Since our device driver will run in kernel mode we cannot block or perform any lengthy tasks. We can, however, compute CPM by counting pulses within 60 seconds interval and then expose this information to user program. But we can do a lot more (compared to radiation logging software running over transport layer on PC and smart phones) - we can notify Windows RT (say in task bar icon) as well as any user program (if it runs, since device driver runs always) about each and every pulse we get from Geiger counter, in real time. That is to have not just CPM, but time stamps for every radiation particle detected with 100 nanosecond accuracy.

Next we need a way for our radiation counter Windows device driver to notify user application upon every particle arrival.

Such interaction between kernel mode Windows device driver and user program can be done in different ways.

The recommended way is to implement DeviceIOControl blocking call from user program into our device driver: this way, user program makes a call into device driver and it sits there until Geiger counter detects a particle, device driver will handle GPIO interrupt, and then that previously blocked call from user application into DeviceIOControl would immediately return and user program would resume its run, thus providing fast response to actual Geiger pulse. User application is then counts this particle and is ready for the next call to wait for another particle arrival to be told about.

It is my understanding that this is the recommended way of doing things per Microsoft Windows device driver development guidelines. To implement it properly takes a bit of work and I only had limited time to spend on this project, so I undertook another, simpler approach.

Please note that the way I did it is different and is considered architecturally incorrect and is not recommended. Nevertheless it works just fine on Windows RT in my test for the purposes of this demonstration.

The simple way I wrote this device driver is based on sharing Windows event handle: user application, when it starts, will create event handle and then tell Windows driver about it. From that point on Windows device driver will signal on that event handle every time a particle is detected; it is up to a user application to wait on this event handle and to react on event signal as often as they wish. Prior to exiting user application unregisters its event handle from our device driver.

Therefore, our radiation counter Windows device driver will have just two Device I\O control codes: one to supply device driver with event handle from user program and another one to tell the driver we no longer need that event handle.

// set event handle
#define IOCTL_GM_CNTR_SET_EVTHNDL     CTL_CODE(FILE_DEVICE_UNKNOWN,  IOCTL_INDEX+1,     \
                                                                     METHOD_BUFFERED,  \
                                                                     FILE_ANY_ACCESS)
// release event handle
#define IOCTL_GM_CNTR_RELEASE_EVTHNDL CTL_CODE(FILE_DEVICE_UNKNOWN,  IOCTL_INDEX+2,     \
                                                                     METHOD_BUFFERED,  \
                                                                     FILE_ANY_ACCESS)

Here is code snippet where event handle from user application is accepted by a driver. Note handle conversion is to be made since user application supplies user mode handle whereas our radiation counter device driver is KMDF driver and hence needs kernel mode handle

C++
…..
case IOCTL_GM_CNTR_SET_EVTHNDL:
 
length=sizeof(pDevContext->hUserEvent1particle);
 
status = WdfRequestRetrieveInputBuffer(Request, length, &ioBuffer, &bufLength);
if(!NT_SUCCESS(status))
{
        TraceEvents(TRACE_LEVEL_ERROR, TRACE_QUEUE, "GM_cnt_DevIoCtrl(ERR!) WdfRequestRetrieveInputBuffer2 failed=%Xh\r\n", status);
        length=0;
        status = STATUS_BUFFER_TOO_SMALL;
        pDevContext->hUserEvent1particle=NULL; // we did not get a valid user handle, so invalidate our copy
}
else
{
// copy user mode event handle into our variable
        RtlCopyMemory(&pDevContext->hUserEvent1particle, ioBuffer, length);
        TraceEvents(TRACE_LEVEL_ERROR, TRACE_QUEUE, " GM_cnt_DevIoCtrl() UserEvtHnd=%ph\r\n", (PVOID)(pDevContext->hUserEvent1particle));
 
        status=ObReferenceObjectByHandle(pDevContext->hUserEvent1particle, // input : user mode event handle
                               DELETE | SYNCHRONIZE, // desired access
                               *ExEventObjectType, // pObjectType,
                               UserMode, &pDevContext->pkevtOneParticle, NULL); // output: kernel mode khandle
 
        if(!NT_SUCCESS(status))
        {
                TraceEvents(TRACE_LEVEL_ERROR, TRACE_QUEUE, " GM_cnt_DevIoCtrl(ERR=%Xh) UserEvtHnd to KEvt failed! \r\n", status);
        }
        else
        {
                TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_QUEUE, " GM_cnt_DevIoCtrl() PKEvt=%ph\r\n", pDevContext->pkevtOneParticle);
        };
        status = STATUS_SUCCESS;
};

The amount of work to be done in KMDF Windows device driver’s ISR shall be kept to a minimum, since such driver runs in kernel mode at high priority, therefore in our ISR all we do is acknowledge an interrupt and then schedule a DPC where we do some more work.

A simplified version of radiation counter Windows device driver ISR code is shown below.

C++
BOOLEAN GMcntDrvIsr(_In_ WDFINTERRUPT Interrupt, _In_ ULONG MessageID) // note runs at IRQL=11
{
        PDEVICE_CONTEXT pDeviceContext=NULL;
        BOOLEAN bResult=FALSE;
        UNREFERENCED_PARAMETER(Interrupt);
        UNREFERENCED_PARAMETER(MessageID);
 
        pDeviceContext = DeviceGetContext(WdfInterruptGetDevice(Interrupt));
 
// just count particles inside of ISR. Do all other work in DPC
        if(NULL!=pDeviceContext)
        {
               pDeviceContext->ulCountInterrupts+=1;
 
// schedule DPC to do all the work at lower IRQL
               bResult=WdfInterruptQueueDpcForIsr(Interrupt);
               if(FALSE==bResult)
               {
                       TraceEvents(TRACE_LEVEL_ERROR, TRACE_DRIVER, "GMcntDrvIsr(ERR) cannot schedule DPC !\r\n");
               };
        }
        else
        {
               TraceEvents(TRACE_LEVEL_ERROR, TRACE_DRIVER, "GMcntDrvIsr(ERR) pDeviceContext is Null\r\n");
        };
 
        bResult=TRUE;
        return bResult;
}

In device driver .inf file I have assigned this radiation counter device class to "Sensors." This is arbitrary, and Windows RT already has its own "Sensors" device class, but my device driver does not conform to their Sensor model, so to avoid confusion of Windows RT regarding my radiation counter device I gave it different class GUID, and it appears in Device Manager under "Sensors" but separately, not in Microsoft Sensors group. Section of radiation counter device driver .inf file shown below demonstrates this:

; GM_counter_Driver1.inf
;
 
[Version]
Signature="$WINDOWS NT$"
Class=System
ClassGuid={78A1C341-4539-11d3-B88D-00C04FAD5171} ; // Microsoft Sensors use {4D36E97D-E325-11CE-BFC1-08002BE10318}
….
[Strings]
SPSVCINST_ASSOCSERVICE= 0x00000002
ManufacturerName="SergeiR" ;
ClassName="Sensors" ;

The radiation counter Windows RT device driver VS2012 project can be downloaded separately in here. Please note that is shall only be built for ARM, not for Win32 or X64.

Testing Device Driver only (on Windows RT)

First we ensure our Windows device driver loads properly and starts without any trouble. To assist us in this step Windows Device Manager can be used first. In the screenshot below Windows Device Manager shows our radiation counter as properly working device.

 Image 2

Next, in order to get more detailed insight into our driver loading and operations the TraceView utility can be used: a number or trace debug statements are placed throughout the radiation counter driver C code, so by running TraceView utility the output from our device driver can be conveniently inspected at run time without the need for Windows kernel debugger.

A screen shot below demonstrates sample trace debug output from our driver at run time. To verify we do get each and every Geiger counter particle detected I have used pulse generator first to ensure that each pulse generates and interrupt and further that each such interrupt is handled in our device driver.

 Image 3

This is what we have accomplished so far is looking like

 Image 4

How high can we go? With particular Windows RT prototype platform I was working on my device driver can operate reliably up to a rate of approx. 400 interrupts per second. Please note that in practice Geiger counter when used as radiation counter in typical situations usually would generate less interrupts, since having 400 interrupts/sec equates to 400 counts/sec which then is equivalent to dangerously high radiation count of 24000 CPM.

Finally, I disconnected pulse generator from GPIO pin and connected actual Geiger counter load resistor as shown on diagram in above. Within seconds my device driver started to dump every particle event of background radiation to TraceView.

So, our radiation counter Windows device driver on Windows RT so far is working well and is even suitable for practical applications in the future: possible dynamic range 0 to 24000 CPM and right inside of Windows RT operating system.

Part 3 Windows RT radiation counter application

While user application can be quite fancy, let’s start with a simple one, and then step by step demonstrate more advance features which can be utilized with our radiation counter Windows device driver.

Simple console test program on Windows RT

Now we can make a simple, minimalistic C console user application to verify interaction with our driver. Also, since our user application is being notified on every radiation particle event, it is our user application which will compute CPM (not device driver) and print it to screen (and to log file if we choose).

Architecturally our simple console test application with device driver looks like this

 Image 5

User application can open our radiation counter in two ways – by its DOS device name or by its fully qualified name, determined by device driver class GUID. Either way works fine with our radiation counter device driver.

Our simplest C test program would look like this (error checking omitted for clarity)

C++
#define DOS_DEV_NAMEW  L"\\\\.\\G-McntDevice"                   // our radiation counter Windows device driver DOS name
const TCHAR* cszParticleEventName=_T("Global\\GMCntPartEvent"); // global space event name
 
int _cdecl main(_In_ int argc, _In_reads_(argc) char *argv[])
{
        int iret=0;
        HANDLE hGMCntDev=NULL;
        HANDLE hEvtParticle=NULL;
        BOOL bResult=FALSE;
        LPSECURITY_ATTRIBUTES psaEvent=NULL;
 
// open our radiation counter device by its DOS name
        hGMCntDev=CreateFile(DOS_DEV_NAMEW, GENERIC_WRITE | GENERIC_READ,
                       FILE_SHARE_WRITE | FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
 
// we need to other processes full access this user app event
        SetUp_ParticleEventAllAccess(psaEvent);
 
        hEvtParticle=CreateEvent(psaEvent, FALSE, FALSE, cszParticleEventName);
 
// register event handle with driver
        bResult=DeviceIoControl(hGMCntDev,
                                      IOCTL_GM_CNTR_SET_EVTHNDL,
                                      &hEvtParticle, sizeof(hEvtParticle), // handle is input to driver
                                      NULL, NULL,                          // no output from driver
                                       (PULONG)(&nBytes), NULL);
 
// now wait for any events on this handle - our device driver will set event on it
// whenever radiation particle is detected
 
// listen to the next 3 particle events, with 10 sec time out
        for(int i=0; i<3; i++)
        {
               dwRet=WaitForSingleObject(hEvtParticle, 10000);
 
               if(WAIT_OBJECT_0==dwRet)
               {
                       printf(" Particle event %d!\r\n", i);
                       iret+=1;
               }
               else if(WAIT_TIMEOUT==dwRet)
               {
                       printf(" Particle event %d timed out\r\n", i);
               }
// compute CPM here
        };
// done waiting for radiation particle events, so
// notify radiation counter device driver to forget our event handle
        bResult=DeviceIoControl(hGMCntDev,
                                      IOCTL_GM_CNTR_RELEASE_EVTHNDL,
                                      &hEvtParticle, sizeof(hEvtParticle), // handle is input to driver
                                      NULL, NULL,                          // no output from driver
                                      (PULONG)(&nBytes), NULL);
// and close device driver
        CloseHandle(hGMCntDev);
        return iret;
}

Here is sample output of radiation counter simple console test program run on Windows RT, where the first three particle events were expected, however, only two were received within 10 seconds. The background radiation in the area I was working is about 18 CPM, which on a long statistical average is equivalent to about 1 particle event in 3 seconds, we got one interval much longer but that how random they are.

C:\GM_cnt_drv>GM_cnt_testapp.exe
Particle event 0!
 Particle event 1!
 Particle event 2 timed out
C:\GM_cnt_drv>...

The project for console simple test applications can be downloaded separately in here. While you can build this test application for any Windows platform (Win32, X64 or ARM), it will properly run only on Windows RT, since the device driver it will look for can only run on Windows RT.

Win32 and .Net Radiation counter applications (Windows RT)

Since our radiation counter C test console program is working fine but not very interactive, let’s move one step forward and make another, more advanced test program with UI, still keeping C as programming language. A simple radiation counter C test Win32 program with CPM calculation, CPM numeric and analog meter-like display is the next step to test (and to stress) our device driver on Windows RT. In addition, let’s also include interrupt rate on our UI, which is calculated anyway when this test program increments radiation particle counter.

Two threads will be needed – one to wait for events to signal from radiation counter device driver, and another to update UI every several seconds when CPM is low or faster if CPM is high. The details of Win32 programming aren’t much relevant these days, so I will just include a screen shot. Interaction with radiation counter device driver is exactly the same as in console test program.

 Image 6

Last thing with our Win32 test program will be a stress. As I have found device driver can handle up to 28000 CPM without much affecting Windows RT performance, so why not to give Win32 real stress test and see if interaction between KMDF driver and user program is reliable and will we will not lose any particle events.

The pulse generator is the only way to test our setup without involving any real hot radioactive materials. With the pulse generator, over 16000 CPM equivalent rate was easily detected and displayed without a problem, here is a screen shot:

 Image 7

The Win32 radiation counter project can be downloaded separately from here. It can be built for any Windows 8 platform (ARM, Win32 or X64).

As primitive looking UI as Win32 old style program is, it would be nice to have something more modern. C# would be naturally a programming language of choice, since it will offer rich graphical UI and also .NET programs will run on Windows RT as well.

.NET will have to utilize P/invoke mechanism to call into our device driver. However, C# doesn’t have an easy way to deal with native Windows objects, namely event handles to pass to our driver. So C# has to use a call back mechanism instead. We thus come to a point when a common middle layer written in C will be most useful – then all the test programs will work through it in the same uniformed way, rather than doing different access from C and from C#.

A side benefit from it is such that radiation counter middle layer can be made multiplatform – on ARM it will work through actual radiation counter Windows device driver, whereas on Win32 and on X64 it will do simulation of radiation counter device driver generated events.

Radiation counter middle layer library for managed applications (all Windows platforms)

With this in mind here is what the final architecture of our tests would look like:

 Image 8

Here are some notes to explain the layout:

  • Console test programs and Win32 test program link statically to middle layer static lib
  • .NET test program loads dynamically middle layer DLL
  • .NET test program uses P/Invoke with common, standard data types to call C functions in middle layer
  • Java test program uses JNI mechanism with common, standard data types to call C functions in middle layer

Now it is the middle layer which creates user mode event handle and registers it with radiation counter device driver (on Windows RT) or starts a simulator thread (on Win32 or X64). Thus the same .NET test application can run on ARM, Win32 or X64 with the only difference that on ARM there are real radiation counter events and on Win32 and X64 they are simulated.

Further, since middle layer deals with device driver interaction, it nicely shares a single user mode event handle with all applications using this middle layer: for example, the Win32 test program can run simultaneously with the .NET test program and count the same radiation particles. The screenshot below demonstrates this situation, however, please note that due to the nature of C# virtual machine there are arbitrary delays in the C# program. Therefore, CPM displayed by the Win32 test program may not be equal to CPM displayed in C# test program, especially at higher rates.

Here is a screenshot of C# test program, running on Windows RT. The dials control used here is from NextUI gauge library.

 Image 9

Finally, another screenshot demonstrating how two test programs run simultaneously using single instance of our Windows radiation counter driver. A test program launched first creates and registers event handle and passes it to device driver. Second (and all subsequent) test programs just re-uses event handle created by first launch of test program.

This is full screen copy so you can see some Windows RT information in the right corner to ensure software described in this article designed for Windows RT in fact does run well on Windows RT. The discrepancy in CPM between C test program and C# test program is due to .NET run time delays interfering with program run.

 Image 10

Source code for these projects can be downloaded separately

  • Radiation counter Static library (all Windows 8 platforms, auto switched to Geiger simulator mode on Win32 and X64)
  • Radiation counter DLL (all Windows 8 platforms, auto switched to Geiger simulator mode on Win32 and X64)
  • Radiation counter C# test application (all Windows 8 platforms)
  • Radiation counter Java test application (Win32 and X64, Geiger simulator mode only)

Pre-built binaries only are available as well in case if you want to get a feel of it – to run on Win32 or X64 version of Windows (I doubt you will have access to unlocked Windows RT development platform, but ARM binaries are available too), however, CodeProject does not allow binaries with the article, so I may come with alternative way if there is considerable interest in binaries.

Points of Interest

This project demonstrated a few things on Windows RT which are experimental and some are architecturally incorrect but fully working.

A prototype of radiation counter built into Windows RT helped me in some unrelated tests to fine tune some other components of Windows RT, when operating system is servicing GPIO interrupts at a high rate.

Even in prototype version this radiation counter has a quite dynamic range of potentially 0 to 24000 counts/minute without losing any single particle event and may lead to other interesting applications based on Windows RT platform.

Revision History

Originally developed for Windows RT. Then tested and works on Windows RT 8.1 as well. In Geiger simulator mode described test programs work also on Windows 7 and Windows 8 Win32 or X64.

License

This article, along with any associated source code and files, is licensed under The Microsoft Public License (Ms-PL)