SI-22Gで精密に測る方法を考えていました。この年末年始休みを使って新作を作り、ガンマ線の測定をしてみました。手始めに普通の環境で。
今回は感度の高いガンマ線用GM管であるSI-22Gを使いました。長さが215mm、直径が19mmもあるため、収容するケースを探すのに苦労しました。結局は持ち運びやすさの観点で、A5ファイルを入れるプラスチック製のケースにしました。薄いのでカバンにも簡単に入れられますし、スペースの空いているところにはACアダプターを収めることもできます。
蓋を開けると次のようになっています。上の写真は#1号機ですが、下は試作時の#0号機です。
回路は次のようにしました。毎度ながら手書きのままですみませんが。
だいぶ前から少しずつの時間を使って考えてきたものです。PICの部分は信号を固定長にするための部品とお考え下さい。原理はこれまでにこの連載に書いてきたとおりですが、精度を上げるためにカウント数をスイッチで指定できるようにしたほか、Arduinoのソフトウェアも大きなカウントに耐えるようにだいぶ改善をしました。そして、CPM値は小数点以下2桁まで記録します。
Arduinoのプログラムは次のとおりです。
/* Radiation Monitor/Recorder for SI-22G V01-12
(c) 2018-2019 Akira Tominaga, All rights reserved.
Function
1) Count GMT signals on selected threshold-mode
2) Calculate CPM (and smoothed s-CPM for quick modes)
3) Convert s-CPM to micro Severt/h and display it
4) Record time & date,and detail CPM & μSv/h into SD
5) Provide Real Time Clock writer function
Major modifications
01-06: count threshold selected by dip switches
01-07: one of calibration points given by GM.txt file
01-08: set file name as "MMDDhhmm.txt" using start time
01-11: record CPM 2nd decimal place & μSv/h 4th decimal
Remarks
A) Prevent double detection for signal-length 80μS.
*/
#include "tm1637display.h"
#include "wire.h"
#include "spi.h"
#include "sd.h">"
// GMT SI-22G specifications”
#define Xp 76680 // CPMx10: naming point-P for SI-22G
#define Yp 8770 // nSv/h at point-P as described in datasheet
// calibration point-Q measured and provided in SD file "GM.txt"
// GM.txt format is CCCCCnnnn CPMx10 and nSv/h at point-Q
// eg.: "006150049" if 61.5 CPM where 0.049 μS/h
char ChrWork[6]; // SD input Area (CPMx10=5char and nSv/h=4char)
int Xq; // CPMx10 at calibration point Q
int Yq; // nSv/h at calibration point Q
// for SI-22G radiation counting
int CtV; // Signal count value threshold. (): usual env.
#define CtV0 20 // Signal count threshold-mode 0 (around 20sec)
#define CtV1 100 // signal count threshold-mode 1 (around 100sec)
#define CtV2 500 // signal count threshold-mode 2 (around 8.6min)
#define CtV3 2500 // signal count threshold-mode 3 (around 43min)
// for GMT type common
int Cpm = 0; // Counted number to calculate CPMx100
long CPMx100; // CPM x 100
int sF = 50; // smoothing factor (%) for quick mode (0 and 1)
float sCPMx100; // smoothed CPMx100
float nSvx10f = 0; // nano Severtx10 = micro Severt/10000
int nSvx10; // integer for nano Severt x 10
char strCPMx100[9]; // editing area for CPM characters
char strnSvx10[7]; // editing area for nSv and μSv characters
// factors to convert CPM to micro Severt/h
float A; // calculation factor to convert CPM to nSv
float B; // calculation constat to convert CPM to nSv
// for micro SD card
File gmLog; // SD file name in this program
#define Cs 10 // Chip-select pin is D10
// other Digital Pins assignment
#define Det 8 // receive detected signal(L=on) from slave
#define Mdb 17 // Mode button (L=on)
#define CLK 6 // TM1637 CLK signal
#define DIO 7 // TM1637 DIO signal
#define Tm 2 // Time button (L=on)
#define St 3 // Set button (L=on)
#define Ctb0 14 // Count threshold setting bit 0
#define Ctb1 15 // Count threshold setting bit 1
TM1637Display display(CLK, DIO);
uint8_t Data[] = { 0x3f, 0x3f, 0x3f, 0x3f };
int k;
const uint8_t SEG_HoLd[] = {0x76, 0x5C, 0x38, 0x5E}; // HoLd
const uint8_t SEG_OFF[] = {0x3F, 0x71, 0x71, 0x00}; // OFF
const uint8_t SEG_SdEr[] = {0x6D, 0x5E, 0x79, 0x50}; // SdEr
// for Real Time Clock
char strYMDHMS[20]; // editing area for calendar and clock
byte vR[8]; // values in RTC registers
int vI[8] = { 0, 0, 0, 0, 0, 0, 0, 0}; // integers for RTC data
#define RTC_addr 0x68
#define mdI 0 // Initial mode
#define mds 1 // ss
#define mdm 2 // mm
#define mdh 3 // hh
#define mdW 4 // WW=Day of Week
#define mdD 5 // DD
#define mdM 6 // MM
#define mdY 7 // YY
#define sS 0
#define mM 1
#define hH 2
int svTime[3] = {0, 0, 0};
void setup()
{
// Serial.begin(9600);
Wire.begin();
pinMode(Det, INPUT_PULLUP);
pinMode(Mdb, INPUT_PULLUP);
pinMode(Tm, INPUT_PULLUP);
pinMode(St, INPUT_PULLUP);
pinMode(Ctb0, INPUT_PULLUP);
pinMode(Ctb1, INPUT_PULLUP);
display.setBrightness(8); // values are valid from 8 to 11
for (k = 0; k < 4; k++) {
Data[k] = SEG_HoLd[k]; // show Hold-on
}
display.setSegments(Data);
// when mode button holded at power-on
if (digitalRead(Mdb) == LOW) {
rtcSet();
for (k = 0; k < 4; k++) {
Data[k] = SEG_HoLd[k]; // show Hold-on
}
display.setSegments(Data);
}
// check SD card
if (!SD.begin(Cs)) {
for (k = 0; k < 4; k++) {
Data[k] = SEG_SdEr[k]; // if error, show SdEr
}
display.setSegments(Data);
return;
}
// read GM.txt for calibration at point Q
gmLog = SD.open("GM.txt");
// get Xq(CPMx10) at point Q
for (k = 0; k < 5; k++) {
ChrWork[k] = gmLog.read();
}
ChrWork[k] = 0x00; // stopper
Xq = atoi(ChrWork); // get integer Xq
// set Yq(nSv/h) at point Q
for (k = 0; k < 4; k++) {
ChrWork[k] = gmLog.read();
}
ChrWork[k] = 0x00; // stopper
Yq = atoi(ChrWork); // get integer Yq
gmLog.close(); // close the file
// Set initial sCPMx100 for smoothing
sCPMx100 = Xq * 10; // Xq is used for initial value
// set file name as MMDDHHMM.txt
rRTC(); // read RTC device
cR2I(); // convert RTC-format to Integers
sprintf(strYMDHMS, "%02d%02d%02d%02d", vI[mdM], vI[mdD], vI[mdh], vI[mdm]);
String s = strYMDHMS;
s.concat(".txt"); // complete file name
gmLog = SD.open(s, FILE_WRITE); // make the file for data
// write column header
gmLog.println("YYYY/MM/DD hh:mm:ss, CPM , uSv/h");
// Serial.println("YYYY/MM/DD hh:mm:ss, CPM , uSv/h");
// Set up CPM-to-nSv/h factors
// 1) between point P(& up) and Q, Y=A*X-B
A = (float)(Yp - Yq) / (Xp - Xq);
B = (float)(Xp * A - Yp);
// 2) between point Q and O(0,0), Y=X*Yq/Xq
// Serial.print("Ax100=");
// Serial.print(A*100);
// Serial.print(" B=");
// Serial.print(B);
// Set counting threshold value CtV
switch ((!digitalRead(Ctb0)) + (!digitalRead(Ctb1)) * 2) {
case 0: CtV = CtV0; sF = 50; break;
case 1: CtV = CtV1; sF = 75; break;
case 2: CtV = CtV2; sF = 100; break; // no smoothing
case 3: CtV = CtV3; sF = 100; break; // no smoothing
}
// Serial.print("CtV=");
// Serial.println(CtV);
// Set svTime at start of loop
rRTC(); // read RTC device
cR2I(); // convert RTC-format to Integers
svTime[sS] = vI[mds];
svTime[mM] = vI[mdm];
svTime[hH] = vI[mdh];
}
void loop()
{
// 1) when GMT signal was detected
if (digitalRead(Det) == LOW) {
Cpm = Cpm + 1;
if (Cpm >= CtV) { // when reached count-value threshold
// get duration
rRTC(); // read RTC device
cR2I(); // convert RTC-format to Integers
int durS = vI[mds] - svTime[sS];
int durM = vI[mdm] - svTime[mM];
int durH = vI[mdh] - svTime[hH];
if (durS < 0) {
durS = durS + 60;
durM--; // this might cause durM=-2 case
}
while (durM < 0) { // including durM=-2 case
durM = durM + 60; // (H increased &| above case)
durH--; // this might cause durH=-2 case
}
while (durH < 0) { // including durH=-2 case
durH = durH + 24; // (D increased &| above case)
}
durS = durS + 60 * durM + 3600 * durH; // duration in second
svTime[sS] = vI[mds]; // save new time
svTime[mM] = vI[mdm];
svTime[hH] = vI[mdh];
// from CPMx100 to nSvx10/h, with sloap A, const B, & slope Cq
// i) between point Q and P&up, Y=(A*X-B)*10
// ii) between point Q and O(0,0), Y=Cq*X where Cq=Yq/Xq
float Cpmfx100 = (float)CtV * 6000.0 / durS;
// Cpm smoothing to sCPMx100
sCPMx100 = (float)Cpmfx100 * (sF / 100.0) + sCPMx100 * (100 - sF) / 100.0 ;
if (sCPMx100 >= Xq * 10) {
nSvx10f = (float)(A * sCPMx100 - B * 10 );
if (nSvx10f < 0) {
nSvx10f = 0;
}
if (nSvx10f > 99990) {
nSvx10f = 99990;
}
} else { // when sCPMx100 <= Xq * 10
nSvx10f = sCPMx100 * Yq / Xq;
}
CPMx100 = Cpmfx100; // save CPM
Cpm = 0; // clear current count
nSvx10 = nSvx10f; // float nSv to integer, to use in ItoC
ItoC(); // Integers to Characters
// Display LED 4 digits as μSv/h
int nSv = (nSvx10f + 5.0) / 10.0; // round nSvx10f and convert to nSv
sprintf(Data, "%04d", nSv); // convert it to characters
for (k = 0; k < 4; k++) { // and convert them to LED images
Data[k] = display.encodeDigit(Data[k] - 0x30);
}
Data[0] = Data[0] + 0x80; // insert dot
display.setSegments(Data); // display Data
// Write SD data using String S
String s = strYMDHMS; // set calendar date
s.concat(",");
// setting CPM
strCPMx100[8] = strCPMx100[7]; // copy stopper
strCPMx100[7] = strCPMx100[6]; // shift 1st dp char
strCPMx100[6] = strCPMx100[5]; // shift 2nd dp char
strCPMx100[5] = 0x2E; // insert period
s.concat(strCPMx100);
s.concat(","); // comma for CSV file
// nano to micro Severt, inserting period
for (k = 6; k >= 2; k--) {
strnSvx10[k] = strnSvx10[k - 1];
}
strnSvx10[1] = 0x2E; // insert period
s.concat(strnSvx10);
gmLog.println(s); // write a SD record
// Serial.println(s);
}
// elese consume time to avoid double detection
delayMicroseconds(80);
} // end of signal detection process
// 2) when Mode pushed, to close SD card
if (digitalRead(Mdb) == LOW) {
gmLog.close(); // close gmLog file
for (int k = 0; k < 4; k++) {
Data[k] = SEG_OFF[k];
}
display.setSegments(Data);
while (1) { // stop process
}
}
}
/* **********************************
User defined functions *
*********************************** */
// read Real-Time-Clock
void rRTC(void) {
Wire.beginTransmission(RTC_addr);
Wire.write(0x00);
Wire.endTransmission();
Wire.requestFrom(RTC_addr, 7);
while (Wire.available() < 7) {} // wait for data ready
for (int i = 1; i < 8; i++) {
vR[i] = Wire.read();
}
Wire.endTransmission();
}
// convert RTC-format to Integers
void cR2I(void) {
vI[mds] = ((vR[mds] & B01110000) / 16) * 10 + (vR[mds] & B00001111);
vI[mdm] = ((vR[mdm] & B01110000) / 16) * 10 + (vR[mdm] & B00001111);
vI[mdh] = ((vR[mdh] & B00100000) / 32) * 20 + ((vR[mdh] & B00010000) / 16) * 10 + (vR[mdh] & B00001111);
vI[mdW] = vR[mdW];
vI[mdD] = ((vR[mdD] & B01110000) / 16) * 10 + (vR[mdD] & B00001111);
vI[mdM] = ((vR[mdM] & B00010000) / 16) * 10 + (vR[mdM] & B00001111);
vI[mdY] = ((vR[mdY] & B11110000) / 16) * 10 + (vR[mdY] & B00001111);
}
// convert Integer contents to characters
void ItoC(void) {
sprintf(strYMDHMS, "20%02d/%02d/%02d %02d:%02d:%02d", vI[mdY], vI[mdM], vI[mdD], vI[mdh], vI[mdm], vI[mds]);
sprintf(strCPMx100, "%07d", CPMx100);
sprintf(strnSvx10, "%05d", nSvx10);
}
// set RTC when mode button pushed at power-on timing
void rtcSet(void) {
byte mC[8] = {0x00, 0x6d, 0x54, 0x76, 0x3e, 0x5e, 0x37, 0x6e};
// 7 seg modeChr 0, 's', 'm', 'h', 'W', 'D', 'M', 'Y'
char Su[3];
int maxvI[8] = { 0, 59, 59, 23, 7, 31, 12, 99};
int minvI[8] = {0, 0, 0, 0, 0, 1, 1, 0};
while (digitalRead(Mdb) == LOW) {}; // wait until Mdb released
int mD = mdY ; // initial mode=mdY
rRTC(); // read RTC device
cR2I(); // convert RTC-format to Integers
// loop waiting for Set button
while (digitalRead(St) == HIGH) {
Data[0] = mC[mD];
Data[1] = 0x48; // " = "
sprintf(Su, "%02d", vI[mD]);
Data[2] = display.encodeDigit(Su[0]);
Data[3] = display.encodeDigit(Su[1]);
display.setSegments(Data);
// Mode button loop within Set button loop
while (digitalRead(Mdb) == HIGH) { // do until Mdb pushed
if (digitalRead(Tm) == LOW) {
vI[mD] = vI[mD] + 1; // increase value
if (vI[mD] == maxvI[mD] + 1) { // if exceeded max
vI[mD] = minvI[mD]; // then set minimum
}
sprintf(Su, "%02d", vI[mD]);
Data[2] = display.encodeDigit(Su[0]);
Data[3] = display.encodeDigit(Su[1]);
display.setSegments(Data);
delay(250);
// if Set on, go out of Time loop
if (digitalRead(St) == LOW) break;
} // repeat TM loop
// if Set on,go out of Mode loop
if (digitalRead(St) == LOW) break;
// else repeat Mode button loop
}
// out of Mode loop within Set loop
while (digitalRead(Mdb) == LOW) {} // until end of pushing
if (digitalRead(St) == LOW) break; // if Set pushed, go out
mD = mD - 1;
if (mD == mdI) {
mD = mdY;
}
} // out of Set loop
while (digitalRead(St) == LOW) {} // until end of Set-pushing
// write RTC
vR[mds] = (vI[mds] / 10) * 16 + vI[mds] % 10;
vR[mdm] = (vI[mdm] / 10) * 16 + vI[mdm] % 10;
vR[mdh] = (vI[mdh] / 20) * 32 + ((vI[mdh] % 20) / 10) * 16 + vI[mdh] % 10;
vR[mdW] = vI[mdW];
vR[mdD] = (vI[mdD] / 10) * 16 + vI[mdD] % 10;
vR[mdM] = (vI[mdM] / 10) * 16 + vI[mdM] % 10;
vR[mdY] = (vI[mdY] / 10) * 16 + vI[mdY] % 10;
Wire.beginTransmission(RTC_addr);
Wire.write(0x00);
for (int i = 1; i < 9; i++) {
Wire.write(vR[i]);
}
Wire.endTransmission();
}
この連載で書いたとおり、CPMからμSv/hへの変換は次のグラフのようにしており、P点はGM管の仕様をそのまま使い、Q点は較正済みのγ線測定器で長時間測った値を採用しています。プログラムからも読み取れるかと思います。
最初に試作した#0号機ではそこに値をあわせてあります。今回その基盤を少し改良した#1号機を1月1日に仕上げました。こちらはそれによる較正をまだしていません。今回その両方を比較したのがこの記事の最初に掲げた図です。ただし較正が必要なのはμSv/hへの変換のためだけであり、もちろんCPM値についてはありのままで較正は不要です。
(SI-22Gの場合、これまで10本ほど比較している限りでは偶々なのかもしれませんが、長く測ると個体によるカウント差が小さいのに驚きます。)
そして測定結果はマイクロSDカードに次の様に書き込まれます。
この装置の基板は次のものです。
切削図はこれ。
また、GMTのソケットをとりつける基板は次のものです。小さな基板ですが。
操作をする際は、ケースの蓋をあけて行います。
電源は電池とACアダプターをスイッチで切り替えるようにし、別のオンオフスイッチを操作します。もし、電池とACアダプターをDCジャックで切り替えると楽なのですが、接触がいずれどうしても問題となります。そこで、筆者は電池側にショットキーダイオードを入れて電池への逆流を防ぐという手を使う場合が多いです。しかし、今回はArduino(ATmega328p)を16MHzで駆動するために、電池電圧をあまり下げられませんからこのようにしました。とはいえ操作は簡単です。
電源オンの前にカウント数の指定をディップスイッチで行ないます。00,01,10,11の4通りが指定できますが、このプログラムではそれぞれ20カウント、100カウント、500カウント、2500カウントを設定しています。最初のグラフは2500カウントの例です。10、11ではカウントが多いので指数平滑法は不要となります、、というより行うと邪魔ですね。プログラムをご覧になるとわかります。
そのほか、音を出したくない時はいつでもスイッチでMuteを指定できるようにしてあります。
電源をオフにする前には、Modeスイッチを押してファイルの書き込みをしておく必要があります。
RTC(リアルタイムクロック)の設定にはMode, Time, Setの3つのタクトスイッチを使います。最近のRTCは数年間ほとんど狂いませんから、普通はこの操作を使用する必要はありませんが。
電源オンをする際にModeを押しながら行えば、時刻設定操作にはいります。Modeをおすごとに、YY=, MM=, DD=, W=(曜日), hh=, mm=, ss=と順次に設定値2桁が表示されるので、変更する部分をTimeスイッチを押して変更し、Modeスイッチで他の変更へ移ります。そして設定が終わったらSetを押した時点で、RTCはその時刻に設定されます。
なお、ディスプレイにはμSv/hを4桁しか表示しませんが、カウント数が多い場合はその下の桁まで有効であるため、SDへの記録へは小数点以下を4桁まで行っています。
今後は線源を使ったテストなども行う予定ですが、時間のあるときに。。。
この作品には、#0号機にも#1号機にもまだラベル等を貼っていませんでしたが、完成後次のように操作がわかりやすいようにラベルを貼りました。
ハンダ面にはコート剤を塗ります(噴き付けます)。
とりあえずこの辺までにしておき、時間のあるときに更新するか、または別の記事として書くことにします。
以下補足です:これをFacebookに少し出したら、放射線量に関するご質問があります。「ふつうはどれくらい?」というのですが、答えにくいですね。環境γ線量については、例えば次のサイトをご覧いただくと地域別のμSv/hがリアルタイムでわかり重宝です。そのようにお答えしておきました。
いっぽう、CPMはもちろんGM管の種類で異なります。また、GM管は管壁を通過するベータ線に対してはγ線よりもずっと敏感です。ですからGM管は色んなモノ(とくにカリウムを含むものなど)に近づけたりせず、また面倒でもベータ線を遮蔽して測ることが必要です。例えば身近な物質に含まれるカリウム40同位体からのベータ線(エネルギーは約1.5MeV以下に分布)は、アルミ板なら3mm厚、樹脂ですと7mm厚程度、鉄板なら1.1mm厚で遮蔽できます。この記事の場合、樹脂の箱に2重に収め2階で測りました。
2重に樹脂ケースに入れケースと合計7mm厚以上にしたとはいえ、和室の畳の上で測りましたので少しカリウムの影響を受けているかもしれませんので今後確認しておくことにします。なぜなら畳表は藺草で畳の芯は乾燥稲わらでできています(最近の畳の芯は樹脂ですが使った和室のはわらだったかと思います)から、結構な量のカリウムが含まれています。→ 後日影響がなかったことを確認しました。不思議なのでよく調べたら、その部屋の畳の芯は樹脂であることがわかりました。つまり畳の芯が何かまでがわかってしまいますね^^
ここからは、ついでの余談となります:カリウムはその中の0.0117 %が放射性同位体カリウム40で、その半減期は12.48億年ですからなくなりません。そのためカリウム1gあたり31.58ベクレルの放射能(机上計算で求められます)を含み、主に強いβ線を出しています。例えば乾燥稲わらの重量のほぼ2%がカリウムですから、畳の芯が稲わらの場合627ベクレル/kgとなります。畳の上ではβ線の遮蔽をしっかり行ない、少し距離をおかないと、GM管のカウント数値は影響を受けます(体に影響があるかといえばその話は別ですが)。
追記:カリウムが出す放射線量の計算を次の記事に書いておきました。
自作器テストのための身近な放射線源8種 - 勝手な電子工作・・
だいぶ、脇道にそれてしまいました。。。
(c)2019 Akira Tominaga, All rights reserved.