/* geiger.c --
* Created: Thu Jan 24 11:54:31 2002 by faith@alephnull.com
* Revised: Tue Mar 26 15:12:17 2002 by faith@alephnull.com
* Copyright 2002 Rickard E. Faith (faith@alephnull.com)
*
* Adapatation by Peer Janssen (peer@baden-online.de):
* Jul 02 2005 - Use of calibration factor 1 as default value
*
* More hacking by Tjerk Schuringa (tjerk@polonai.se)
* Interface for RKSB-104 Russian geiger counter
* Make sure your motherboard's IDC header pinout corresponds to
* the 9-pin D connector!
* 21 July 2012
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
* $Id$
*
*/
/* This code collects data from the Aware Electronics RM-60 geiger
* counter. It probably works with other geiger counters from Aware
* Electronics, but I haven't tried any others (although the output will
* not be in uR/hr -- please update the gLog function to compute the
* uR/hr correctly for other models).
*
* The pin out of the 4 wires from the geiger counter are as follows:
*
* RM-60 RS-232 DB-9 (Male)
* Ground SG 5
* Signal RI 9 (goes low when an event occurs)
* Positive DTR 4
* Negative RTS 7
*
* I did all my initial testing with a breakout box with only these 4
* pins and pin 1 (FG) connected. Then I plugged in the DB-9 connector
* to the RM-60 cable and did more tests without the breakout box. I
* had the best success with the standard serial port (/dev/ttyS0) and
* wasn't able to get things to work with a port on an ISA-bus Cyclades
* Cyclom-Y serial card (perhaps because it doesn't put out enough power
* to power the device? -- I didn't explore further).
*/
#define DEFAULT_TTY "/dev/geiger"
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <termios.h>
#include <fcntl.h>
#include <signal.h>
#include <time.h>
#include <sys/ioctl.h>
#include <linux/serial.h>
#define GAVERAGE 5 /* Number of minutes to average over */
#define GWAIT 60 /* Number of seconds to wait (for testing) */
typedef struct gData {
time_t t;
unsigned long count;
} gDataT;
static int fd;
static double click[GAVERAGE];
static int currentClick, maxClick;
static gDataT d0, d1;
static const char *logfile;
static char filename[2048];
static FILE *str;
static void gLog(gDataT *d0, gDataT *d1)
{
double current, average, total;
int i;
/* double factor = 1000.0 / 1050.0; /* RM-60 */
double factor = 1; /* simple counting of pulses */
/* Compute uR/hr for previous minute.
* Note that, for the RM-60,
* counts/minute * 1000/1050 = uR/hr
* (see http://www.aw-el.com/specs.htm) */
time_t seconds = d1->t - d0->t;
current = (double)(d1->count - d0->count) * factor * 60.0 / seconds;
/* Maintain table for computing average */
click[currentClick++] = current;
if (currentClick > maxClick) maxClick = currentClick;
if (currentClick >= GAVERAGE) currentClick = 0;
/* Compute average */
for (total = 0, i = 0; i < maxClick; i++) total += click[i];
average = maxClick ? (total / maxClick) : 0.0;
str = fopen(logfile, "w");
fprintf((logfile) ? str : stdout, "%.0f", current);
fclose(str);
}
static void gRead(gDataT *data)
{
struct serial_icounter_struct icount;
time(&data->t);
ioctl(fd, TIOCGICOUNT, &icount);
data->count = icount.rng;
}
static void gHandler(int sig)
{
gRead(&d1);
gLog(&d0, &d1);
d0 = d1;
alarm(GWAIT);
}
static void gOpen(const char *port)
{
struct termios term;
int info;
if ((fd = open(port, O_RDWR|O_NOCTTY)) < 0) {
perror( __FUNCTION__ );
fprintf( stderr, "Cannot open \"%s\"\n", port );
exit(1);
}
/* This prevents two daemons from
* running simultaneously, but it
* otherwise optional. */
if (lockf(fd, F_TLOCK, 0) < 0) {
perror(__FUNCTION__);
fprintf(stderr, "Cannot lock \"%s\"\n", port);
close(fd);
exit(1);
}
if (tcgetattr(fd, &term) < 0) {
perror( __FUNCTION__ );
fprintf( stderr, "tcgetattr failed\n" );
exit(1);
}
/* Raw */
cfmakeraw(&term);
term.c_cflag |= HUPCL;
term.c_cc[VMIN] = 0;
term.c_cc[VTIME] = 0;
cfsetospeed(&term, B2400);
cfsetispeed(&term, B2400);
/* Set */
tcsetattr(fd, TCSANOW, &term);
/* Turn on power -- DTR high, RTS low */
info = TIOCM_DTR;
ioctl(fd, TIOCMBIS, &info);
info = TIOCM_RTS;
ioctl(fd, TIOCMBIC, &info);
sleep(1);
}
static void help( void )
{
static const char *help_msg[] = {
"",
"-t <tty> specify tty (default = " DEFAULT_TTY ")",
"-d increase debugging level",
"-l logfile name in strftime(3) format",
"-h give this help",
0 };
const char **p = help_msg;
fprintf( stderr, "geiger 1.0\n");
fprintf( stderr, "Copyright 2002 Rik Faith (faith@alephnull.com)\n" );
while (*p) fprintf( stderr, "%s\n", *p++ );
}
int main( int argc, char **argv )
{
int debug = 0;
const char *tty = DEFAULT_TTY;
int c;
struct sigaction sa;
while ((c = getopt( argc, argv, "t:dl:")) != EOF)
switch (c) {
case 't': tty = optarg; break;
case 'd': ++debug; break;
case 'l': logfile = optarg; break;
default: help(); exit(0);
}
gOpen(tty);
memset(&sa, 0, sizeof(sa));
sa.sa_handler = gHandler;
sigaction(SIGALRM, &sa, NULL);
gRead(&d0);
alarm(GWAIT);
for (;;) {
/* If we're debugging, we want the ioctl to return with each
* change so that we can print something. This would be useful
* if we were trying to record the time between events.
* However, if we aren't debugging, then we can let the kernel
* service the interruption and keep count -- we don't need to
* return to user space to have that happen. See the code in
* linux/drivers/char/serial.c to see how this works.
*
* Note that this ioctl can also return EIO and EINTR, so if
* you're timing events, be sure to check that an event actually
* happened. Note that the delivery of SIGALRM causes the ioctl
* to return. */
ioctl(fd, TIOCMIWAIT, debug ? TIOCM_RNG : 0);
if (debug) {
time_t t;
time(&t);
printf("%lu\n", t);
}
}
return 0;
}