またまた線量計の工作ですみませんが、いくつか特別に調べたいことがあり本機の製作をだいぶ前から考えていました。毎日少しずつ時間をかけて早く作り終えていたのですが、確実な動作を行わせる調整が必要となり、ようやく完全なものになりました。
測定時間としては用途に合わせて、次の8種類からDIPスイッチで指定します:1分、3分、10分、30分、1時間、3時間、6時間、012時間。本機は、γ線の感度が高いGM管であるSI22Gを2本搭載して、独立にカウントしています。毎分100CPM以上のカウントがとれることから測定数値は安定しており、指数平滑法などのスムージングは必要ありません。
回路は次のようにしています。毎度汚い手書きですみませんが。
Arduino(ATMega328P)が主体ですが、GMTの信号を固定する目的のために各1個のPICマイコンを使っています。また、正確なタイマーとするためにRTC(リアルタイムクロック)の出すスクウェア信号(SQW)を数えるために、もう1個PICマイコンを使っています。これにより高精度の測定間隔にしています。
たとえば、各GMTの信号取出しを受け持つPICは、次の写真のように、GMTの信号1)を検出したら、それを80µSの固定長信号2)にし、サウンダーに対する4kHz信号3)を2mS出力しつつ、併せて4)LEDを2mS点灯させています。
さらに細かいことを書けば、GMTの信号が最大でも3µS以内に点検されるようにアセンブラーで組んでいます。2)の信号をArduinoへ送る間はGMT固有のdead time (検出後に次の検出が可能な時間間隔GMTの種類に応じて例えば90µS~190µS)の中にあるので監視の必要がありませんが、3)4)の処理中には常に最大3µS以内の間隔でGMTを点検し、新たな信号を検出すれば直ちに上の図の処理がそこから改めて行われます。このような短時間内での細かな芸当は、アセンブラーの利点ですね。もっとも、このPICで必須なのは1)に応じて2)を出すことだけです。これによりArduinoで2つのGMTからの信号を取りこぼすことがなくせるわけです。それだけですとこのPICには余裕があるので3)と4)もやっているだけのことです。この処理内容は前に書いた信号検出の記事などで紹介したかと思います。
Arduino側では、2つのPICから来る信号2)を100%過不足なくとらえるため、普通ではないことを少し行っています。1つ目は、ArduinoのLoop時間(D5ピンで計測できる回路とプログラムにしています)の変動を抑えるため、Loop内での変数の初期設定を避けています(そうするとやむなくGlobal変数を使います)。Loop数回に1度生じる変数領域の開放に時間を消耗するようですから。それによって1つの信号をダブって検出することがないように処理が作れます。
2つ目は、時間が僅かに変動しがちなタイマー割込みでの測定を避け、かつ長い時間間隔でも同じ単純な処理とするために、高精度のRTCが出す信号を使う方式にしています。その処理に別のPICを用いています。こちらの処理は単純ですが。
まずブレッドボードとArduino-Unoで作ったのが次の写真です。
ブレッドボードの配線は単純です。拡大すると次のようになっています。
問題なく動き、次の動画のようになります。
GMT信号、検出信号、Arduinoでの捉え方について、オシロとLogicアナライザで十分測定し、動きが正確であることを確認しました。ここまでは早かったです。そして例えば1時間単位のバックグラウンド計測では次のようなoutputを得ました。
これをグラフにすると次のようになります。
Arduinoのプログラムは次のように作りました。
/* Dual-GMT Radiation Monitor/Recorder V00-02
(c) 2019 Akira Tominaga, All rights reserved.
Function
1) Count signals of GMT-A and GMT-B
2) Calculate CPM and μSv/h to record to SD at specified timings
3) Provide Real Time Clock writer function
Major modifications
00-01: Test pin at D5 for timing measurement
00-02: Reflect dual GMT calibration data
Remarks
A) Prevent double detection for each signal-length 80μS.
- One loop cycle takes time from 25μS to 32 μS
- When updating SD and display, from 12mS to 20mS
*/
#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"
// txt is CCCCCnnnnCCCCC CPM-Ax10, nSv/h & CPM-Bx10 at point-Q
// eg.: "00615004900618" if 61.5 CPM-A, 0.049 μS/h, 61.8 CPM-B
char ChrWork[6]; // SD input Area (CPMx10=5char and nSv/h=4char)
int Xq; // CPMx10 at calibration point Q for GMT-A & ave
int Yq; // nSv/h at calibration point Q
// for SI-22G radiation counting
long DurV; // Duration value in second
#define DurV0 60 // 60 seconds
#define DurV1 180 // 3 minutes
#define DurV2 600 // 10 minutes
#define DurV3 1800 // 30 minutes
#define DurV4 3600 // 1 hour
#define DurV5 10800 // 3 hours
#define DurV6 21600 // 6 hours
#define DurV7 43200 // 12 hours
// for GMT type common
long CntA = 0; // GMT-A Counter
long CntB = 0; // GMT-B Counter
long CPMx100; // A and B average CPM x 100, for μSv/h conversion
float Cpmfx100; // float value for μSv/h conversion
long nSv; // nano Severt/h
float nSvx10f = 0; // nano Severtx10 = micro Severt/10000
long nSvx10; // long for nano Severt x 10
char strCPM[7]; // 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
// Digital pins assignment
#define Mdb 2 // Mode button (L=on)
#define Tm 3 // Time button (L=on)
#define St 4 // Set button (L=on)
#define TstP 5 // Test pin for timing measurement
#define CLK 6 // TM1637 CLK signal
#define DIO 7 // TM1637 DIO signal
#define DetA 8 // detected signal(L=on) from slaveA
#define DetB 9 // detected signal(L=on) from slaveB
#define Dur0 14 // duration setting bit 0
#define Dur1 15 // duration setting bit 1
#define Dur2 16 // duration setting bit 2
#define TimeSig 17 // Time Signal from timer (L=on)
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
// for application logic
#define nchkN 5 // no-check loops after GMT sig detected
int nchkA=0; // initial value = 0
int nchkB=0; // initial value = 0
String s; // string type work area
boolean tmgI=HIGH; //Indicator for timing measurement at TstP
void setup()
{
// Serial.begin(9600);
Wire.begin();
pinMode(TstP,OUTPUT);
// check point to see AVR awake ** test only **
// tmgI=tmgI^HIGH; // H/L to L/H ***
// digitalWrite(TstP,tmgI); // for test *****
// delay(200); // for test *****
// tmgI=tmgI^HIGH; // L/H to H/L ***
// digitalWrite(TstP,tmgI); // for test *****
// set RTC SQW pin to one-second block-wave mode
Wire.beginTransmission(RTC_addr);
Wire.write(0x0E); // point RTC control register
Wire.write(0x00); // specify 1-Hz square output
Wire.endTransmission();
pinMode(TimeSig, INPUT_PULLUP);
pinMode(DetA, INPUT_PULLUP);
pinMode(DetB, INPUT_PULLUP);
pinMode(Mdb, INPUT_PULLUP);
pinMode(Tm, INPUT_PULLUP);
pinMode(St, INPUT_PULLUP);
pinMode(Dur0, INPUT_PULLUP);
pinMode(Dur1, INPUT_PULLUP);
pinMode(Dur2, 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 held 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");
// 1)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
// 2)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
// 3)get Xq2(CPMx10) at point Q
for (k = 0; k < 5; k++) {
ChrWork[k] = gmLog.read();
}
ChrWork[k] = 0x00; // stopper
int Xq2 = atoi(ChrWork); // get integer Xq2
Xq = (Xq2 + Xq )/2 ; // get the average Xq
gmLog.close(); // close the calibration file
// set output file name as MMDDHHMM.txt, using start-time
rRTC(); // read RTC device
cR2I(); // convert RTC-format to Integers
sprintf(strYMDHMS, "%02d%02d%02d%02d", vI[mdM], vI[mdD], vI[mdh], vI[mdm]);
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,CountA,CountB, uSv/h");
// Serial.println("YYYY/MM/DD hh:mm:ss,CountA,CountB, 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 duration value DurV
switch ((!digitalRead(Dur0)) + (!digitalRead(Dur1)) * 2 + !digitalRead(Dur2) * 4) {
case 0: DurV = DurV0; break;
case 1: DurV = DurV1; break;
case 2: DurV = DurV2; break;
case 3: DurV = DurV3; break;
case 4: DurV = DurV4; break;
case 5: DurV = DurV5; break;
case 6: DurV = DurV6; break;
case 7: DurV = DurV7; break;
}
// wait for some event before loop,here,if needed
}
void loop()
{
// 1) GMT signal detections
if (nchkA==0){ // if A is to be counted
if (digitalRead(DetA) == LOW) {
CntA++;
nchkA=nchkN;
}
}
if (nchkB==0){ // if B is to be counted
if (digitalRead(DetB) == LOW) {
CntB++;
nchkB=nchkN;
}
}
// 2) when Time-signal was detected
if (digitalRead(TimeSig) == LOW) {
// 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
Cpmfx100 = (float)(CntA + CntB) * 3000.0 / DurV;
CPMx100 = (float)Cpmfx100;
if (CPMx100 >= Xq * 10) {
nSvx10f = (float)(A * CPMx100 - B * 10 );
if (nSvx10f < 0) {
nSvx10f = 0;
}
if (nSvx10f > 99990) {
nSvx10f = 99990;
}
} else { // when CPMx100 <= Xq * 10
nSvx10f = (float)CPMx100 * Yq / Xq;
}
nSvx10 = nSvx10f; // float nSv to integer
rRTC(); // read RTC device
cR2I(); // convert RTC-format to Integers
sprintf(strYMDHMS, "20%02d/%02d/%02d %02d:%02d:%02d", vI[mdY], vI[mdM], vI[mdD], vI[mdh], vI[mdm], vI[mds]);
sprintf(strnSvx10, "%05d", nSvx10);
// Display LED 4 digits as μSv/h
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
s = strYMDHMS; // set calendar date
s.concat(","); // comma for CSV file
// setting CntA and CntB
sprintf(strCPM, "%06d", CntA);
s.concat(strCPM);
s.concat(",");
sprintf(strCPM, "%06d", CntB);
s.concat(strCPM);
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);
CntA = 0; // clear CntA
CntB = 0; // clear CntB
} // End of TimeSig detection process
// 3) 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
}
}
if (nchkA>0) nchkA--; // if no-check-counter-A > 0, decrease it
if (nchkB>0) nchkB--; // if no-check-counter-B > 0, decrease it
// delayMicroseconds(1); // make one loop time >= 20 μS
tmgI=tmgI^HIGH; // change HIGH and LOW indicator
digitalWrite(TstP,tmgI); // and write to TstP
}
/* **********************************
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);
}
// 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();
}
上のプログラムでは合計するとスケッチが容量の62%、グローバル変数が容量の67%を使い、まだ余裕がだいぶあります。Loop内のローカル変数で済むものを、わざわざグローバル変数を用いている理由は前に述べたとおりです。
種々の線源で感度を確認しました。このことについては、回を改めて別の記事で書こうと思います。
そして基板を次のようにデザインしました。
CNCで次のように削りました。
ここまでくれば製作は簡単です。趣味の時間を残り数回つかって組みあがりました。
ところがこれから完成までが少してこずったのです。ブレッドボードでは全くでなかったアナログ回路の症状(2つのGMT検出信号のクロストーク)が、でました。
最初はGM管AからBへも、BからAへも1V近いクロストークがでています。PICのデジタルピンで検出されるのは2.6V付近以下であるため、検出ミスは生じていませんが、このままでは納得できないですね。
考えてみれば、このGMTの仕様上、僅か10pFで信号を検出しています。その先のインピーダンスは330kΩにしているため、ちょっとした線の引き回しや信号線のクロスが問題となります。
そもそもB側(上のグラフでは青)は、5cmほどのリード線がずいぶんノイズを拾っているのがわかります。とはいっても、これをシールド線にするわけにもいきません。細いシールド線はその長さでも10pF以上の容量を持っていますので。そこで直下に銅箔テープのグランド板をセットし、Bアノードのリード線をAのアノードから遠ざけて固定しました。
さらに、信号の載りやすいAB間のクロスを避けるために、B用高圧回路のジャンパーを抵抗に変えその先にキャパシターを入れて平滑回路としました。
ついでに330kΩの抵抗を300kΩに変更し、可能な限りインピーダンスを下げてみました。
そしてクロストークは確実に半分以下となりましたが、念のために多数回を測定。どんな場合も検出ミスが確実に防げる状態へ大幅に改善しました。
若干残るクロストークは配線のせいではなく、そもそもこのGMTを近い距離に並べているためにアノードソケット間で相互に影響を与えるためです。口金の基板のパターンをやすりで削って相互の距離を遠ざけました。間に鉄板を入れるとさらに減りますがそこまでする必要はないので、これで完成としシールを貼りました。
このトラブルで少し時間がかかってしまい、今回の記事までの投稿間隔があいてしまいました^^;
さて、これを使って試したいことが色々あります。もう数台作って実験を開始したいと思います。それについてはいつかまた掲載したいと思います。
最近デジタルばかりを扱うために、つい油断をしてしまったアナログ回路でした。本機を裏から見ると少しみっともない状態。次の版からは直すようにします。
なお、この製作過程で気付いたことですが、SI-22GというGMTは信号の強さには少し個体差がありますが長時間のカウントに個体差が最大1%ほどしかないのに驚きました(8本試した限りですが)。形が大きいので誤差が相対的に少なく同一特性になりやすいのでしょうか、わかりませんがとにかく昔のGMTなのに優秀であり、凄いことだと思いました。
今回はこの辺で。。
©2019 Akira Tominaga, All rights reserved.