勝手な電子工作・・

勝手なオリジナル電子工作に関する記事を書きます

デュアルGMTレコーダー

f:id:a-tomi:20190215185150j:plain

2本のGMTを独立させてカウントする線量計です。

またまた線量計の工作ですみませんが、いくつか特別に調べたいことがあり本機の製作をだいぶ前から考えていました。毎日少しずつ時間をかけて早く作り終えていたのですが、確実な動作を行わせる調整が必要となり、ようやく完全なものになりました。

測定時間としては用途に合わせて、次の8種類からDIPスイッチで指定します:1分、3分、10分、30分、1時間、3時間、6時間、012時間。本機は、γ線の感度が高いGM管であるSI22Gを2本搭載して、独立にカウントしています。毎分100CPM以上のカウントがとれることから測定数値は安定しており、指数平滑法などのスムージングは必要ありません。

回路は次のようにしています。毎度汚い手書きですみませんが。

f:id:a-tomi:20190215191501j:plain

Arduino(ATMega328P)が主体ですが、GMTの信号を固定する目的のために各1個のPICマイコンを使っています。また、正確なタイマーとするためにRTC(リアルタイムクロック)の出すスクウェア信号(SQW)を数えるために、もう1個PICマイコンを使っています。これにより高精度の測定間隔にしています。

たとえば、各GMTの信号取出しを受け持つPICは、次の写真のように、GMTの信号1)を検出したら、それを80µSの固定長信号2)にし、サウンダーに対する4kHz信号3)を2mS出力しつつ、併せて4)LEDを2mS点灯させています。

f:id:a-tomi:20190227140410j:plain

PICによるGMT信号検出時の処理

さらに細かいことを書けば、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で作ったのが次の写真です。

f:id:a-tomi:20190215191749j:plain

ブレッドボードの配線は単純です。拡大すると次のようになっています。

f:id:a-tomi:20190215191858j:plain

問題なく動き、次の動画のようになります。

www.youtube.com

GMT信号、検出信号、Arduinoでの捉え方について、オシロとLogicアナライザで十分測定し、動きが正確であることを確認しました。ここまでは早かったです。そして例えば1時間単位のバックグラウンド計測では次のようなoutputを得ました。

f:id:a-tomi:20190215194555j:plain

測定間隔60分の場合

これをグラフにすると次のようになります。

f:id:a-tomi:20190215194736j:plain

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内のローカル変数で済むものを、わざわざグローバル変数を用いている理由は前に述べたとおりです。

種々の線源で感度を確認しました。このことについては、回を改めて別の記事で書こうと思います。

そして基板を次のようにデザインしました。

f:id:a-tomi:20190215200415j:plain

CNCで次のように削りました。

f:id:a-tomi:20190215200513j:plain

ここまでくれば製作は簡単です。趣味の時間を残り数回つかって組みあがりました。

 

ところがこれから完成までが少してこずったのです。ブレッドボードでは全くでなかったアナログ回路の症状(2つのGMT検出信号のクロストーク)が、でました。

最初はGM管AからBへも、BからAへも1V近いクロストークがでています。PICのデジタルピンで検出されるのは2.6V付近以下であるため、検出ミスは生じていませんが、このままでは納得できないですね。

f:id:a-tomi:20190215201041j:plain

当初のAからbへのクロストーク

f:id:a-tomi:20190215201123j:plain

当初のBからAへのクロストーク

 考えてみれば、このGMTの仕様上、僅か10pFで信号を検出しています。その先のインピーダンスは330kΩにしているため、ちょっとした線の引き回しや信号線のクロスが問題となります。

そもそもB側(上のグラフでは青)は、5cmほどのリード線がずいぶんノイズを拾っているのがわかります。とはいっても、これをシールド線にするわけにもいきません。細いシールド線はその長さでも10pF以上の容量を持っていますので。そこで直下に銅箔テープのグランド板をセットし、Bアノードのリード線をAのアノードから遠ざけて固定しました。

f:id:a-tomi:20190215203901j:plain

配線Bへの処置

さらに、信号の載りやすいAB間のクロスを避けるために、B用高圧回路のジャンパーを抵抗に変えその先にキャパシターを入れて平滑回路としました。

f:id:a-tomi:20190215203957j:plain

B側高圧部への変更

ついでに330kΩの抵抗を300kΩに変更し、可能な限りインピーダンスを下げてみました。

そしてクロストークは確実に半分以下となりましたが、念のために多数回を測定。どんな場合も検出ミスが確実に防げる状態へ大幅に改善しました。

f:id:a-tomi:20190216100914j:plain

改善後

若干残るクロストークは配線のせいではなく、そもそもこのGMTを近い距離に並べているためにアノードソケット間で相互に影響を与えるためです。口金の基板のパターンをやすりで削って相互の距離を遠ざけました。間に鉄板を入れるとさらに減りますがそこまでする必要はないので、これで完成としシールを貼りました。

このトラブルで少し時間がかかってしまい、今回の記事までの投稿間隔があいてしまいました^^;

f:id:a-tomi:20190215202733j:plain

蓋をあけたところ

さて、これを使って試したいことが色々あります。もう数台作って実験を開始したいと思います。それについてはいつかまた掲載したいと思います。

最近デジタルばかりを扱うために、つい油断をしてしまったアナログ回路でした。本機を裏から見ると少しみっともない状態。次の版からは直すようにします。

f:id:a-tomi:20190215204246j:plain

裏から見るとグランドの延長が少しみっともないか?

なお、この製作過程で気付いたことですが、SI-22GというGMTは信号の強さには少し個体差がありますが長時間のカウントに個体差が最大1%ほどしかないのに驚きました(8本試した限りですが)。形が大きいので誤差が相対的に少なく同一特性になりやすいのでしょうか、わかりませんがとにかく昔のGMTなのに優秀であり、凄いことだと思いました。

今回はこの辺で。。

 

©2019 Akira Tominaga, All rights reserved.