勝手な電子工作・・

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

Arduinoでの自作ガイガーカウンター解説-#5プログラム

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

ブレッドボードでの試作にプログラミングします

構想が固まったらプログラムをデザインします。

ガイガーカウンターにまつわるところはプログラミングする前によく考えます。GMTの仕様の指定方法ですが、1回目の記事に書いたキャリブレーションのポイントP,Qを次のように設定します。

まずQは、最終的にはできるだけ正確な他のガンマ線測定機器で測った環境中で本器が数えるCPMの平均値を入れるのですが、最初はわかりませんからPとの比例などを使い適当な値を入れます。

PはGM管の仕様から設定します。ここでは3回目の記事に書いた通り、製品仕様のCo60からCs137に設定しなおしています。そのような高い放射線源の準備は困難のため、製品の仕様を信じて使うわけです。CPMのカウントは正確である必要がありますが、μSv/hでの表示自体がもともと精度の高いものではなく、有効数字1桁程度と思います。

とはいっても、正確な機器で実際にキャリブレーションするQの値に近い領域では正確といえるわけですし、変化や差を見る場合には分解能を保つことが重要だと思います。本器では0.001μS/hの単位まで表示します。また、秒単位の計測ですからカウント指定値は1桁の数値は避けて2桁以上の値にします。とはいえ続けてカウントするわけなので、前後の値に細かい差異は含まれ、結果としては正しくとらえられることになります。


// GMT specifications
#define Xp 11280     // CPMx10: point-P for SBM-20
#define Yp 8770      // nano-Sv/h at point-P
#define Xq 156       // CPMx10 measured point-Q
#define Yq 49        // nano-SV/h at point-Q 
#define sF 50        // smoothing factor (%)

単なる#defineなのでメモリーなどを消耗しませんが、小数点を含むものは定義できない(と思います)ので、上記のように小数点1位まで欲しいCPMの値は10倍、μSv/hの値はnSv/h(ナノシーベルト、μSvの千倍)の単位にして定義することにします。

また平滑化指数は上記のように%で定義しました。

このようにすればGM管が変ってもキャリブレーションポイントの値(Q点)が変っても、上だけを直せば済むわけですから。また、平滑化指数の変更も自在です。

そしてsetupの中で必要な係数を次のように計算することにします。


  // Set up CPM-to-nSv/h factors
  // 1) between point P 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=Cq*X
  Cq = (float)Yq / Xq;

ピンアサインメントなども同じことですね。intなどの定数で定義することもできますが、メモリーを無駄にするので#defineがよいですね。


    // Digital Pins assignment
#define Det 2                   // receive detected signal(L=on) from slave
#define Mdb 4                   // Mode button (L=on)
#define CLK 5                   // TM1637 CLK signal
#define DIO 6                   // TM1637 DIO signal
#define Tm 7                    // Time button (L=on)
#define St 8                    // Set button (L=on)

単純なプログラムはそのままコーディングしてしまいますが、少し込み入っている場合はそうはいきません。フローチャートなどを紙に書くのもよいですが、筆者はテキストエディターを使って次のような構造を書きつけておくのが普通です。


プログラムRadMonitor-SBM20-BB-Cnt30の仕様
(c)2018 Akira Tominaga, All rights reserved.

include TM1637Display.h
include Wire.h
include SPI.h
include SD.h
GMT定数の定義
カウント回数の指定
他の変数の定義
ピンのアサインメント
Display表示固定値のセット
RTC関連の設定

Setup
	Pinの設定
	Display設定
	ボタンを押しながら電源オンの場合
		RTCの設定をする rtcSet()
	SDカード設定(filename=GMnnn)
	SDへデータヘッダーの書き込み
	CPM to μS/h 変換のための係数(A、B、とC)を計算
	スタート時刻の取得

Loop
	1)GMT信号が検出された場合
		カウントを1増やす
		カウントが指定数に達した場合
			時刻を取得して経過時間を計算
			CPMを計算
			CPM(X)からナノシーベルト/h(Y)へ変換
				P点とQ点の間またはそれより大きい場合
					Y=AX-B
				Q点と原点の間の場合
					Y=CX
			SDへデータを書き込む
		カウントが指定数に達していない場合
			何もせず80μSを消耗する(ダブルカウントを防ぐ)
	2)ボタンが押された場合
		SDカードをCloseする
		DisplayにOffと表示する
		処理を停止する

ユーザー定義関数
	read Real-Time-Clock rRTC()
	convert RTC-format to Integers cR2I()
	convert Integers to characters ItoC()
 	set RTC time rtcSet()
		(電源オン時にボタンが押されていると呼ばれる)
		ボタンが離されるのを待つ
		RTCを読む rRTC()
		値をIntegerに変換するcR2I()
		最初のモードを年(Y)に設定する
		Setボタンが押されるまでは次を繰り返す
			モードボタンが押されるまで次を行う
			ディスプレイにモード文字=と読んだ値を表示する
			Timeボタンが押されている間は
				その値を最小最大値間で1ずつ変化させる
				変化させた値を文字にする ItoC()
				その文字をディスプレイ3-4桁に表示
			モードボタンが押されたら
				Modeボタンが離されるのを待つ
				モードを次の値にする
		Setボタンが押されたら繰り返しを抜ける
			Setボタンが離されるのを待つ
			Integerの数値をRTCフォーマットに変換する
			RTCに書き込む

以上

 

ExcelでもTeraPadなどでもよいのですが、構造を示す大まかなシュードコード(pseudo-code)です。正式にはwhileやif、switchなどはそのまま記述しますが、その記述がなくてもわかりますから省略しています(不安な方はきちんと記述されることをお勧めします)。

正しく動くかどうか、考慮漏れがないかどうかなどを、この段階で何度か確認してからコーディングをすれば、大きなプログラムでもかなり早く完成できます。むしろプログラムが小さくできますし、無駄な時間が防げます。いきなり書き始めてぐちゃぐちゃになると、一見早そうですが完成はかえって大幅に遅れますね。皆様ご経験のとおりです。

設計書を見ながらコーディングをすると、大きな間違いが防げます。こうするとまわりくどいようですが、結果に至るのはとても早いです。小さいプログラムは別として。次がコーディング内容です。#include の<>の中身がすっぽりぬけて表示されますが、そこは次のように読んでください。

#include <TM1637Display.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>


    /* Radiation Monitor
    for SBM-20 on Bread board
    (c) 2018 Akira Tominaga, All rights reserved.
    version 01-03s
     Function
     1) Count Detected signals
     2) At threshold,calculate CPM & s-CPM(smoothed CPM)
     3) Convert s-CPM to micro Severt/h and display it
     4) Record date, time, CPM, s-CPM and μSv/h to SD
     5) Provide Real Time Clock setting operations
    Remarks
     A) Avoid double detection of Det signal for 80μS
*/
#include 
#include 
#include 
#include 

// GMT specifications
#define Xp 11280     // CPMx10: point-P for SBM-20
#define Yp 8770      // nano-Sv/h at point-P
#define Xq 156       // CPMx10 measured point-Q
#define Yq 49        // nano-SV/h at point-Q 
#define sF 50        // smoothing factor (%)

// for radiation counting
#define CntV 30         // Signal count for CPM calculation
int Cpm = 0;            // Counted number until threshold CntV
int CPMx10;             // CPM x 10 value
int sCPMx10 = Xq;      // smoothed CPMx10 initial value
int Nsv = 0;           // Nano Severt value
char  strCPMx10[7];    // editing area for CPM characters
char  strsCPMx10[7];   // editing area for smoothed CPM characters
char  strNsv[6];       // editing area for Nsv and uSv characters
float A;               // calculation factor to convert CPM to nSv
float B;               // calculation constat to convert CPM to nSv
float Cq;              // calculation factor in weak Nsv zone

// for micro SD card
File gmLog;
#define Cs  10        // Chip-select for D10 pin
char fname[10] = "gm000.txt";   // file name to increase number
int  fnum = 0;                  // initial file name number
char fnumc[4];                  // editing area for file name

// Digital Pins assignment
#define Det 2                   // receive detected signal(L=on) from slave
#define Mdb 4                   // Mode button (L=on)
#define CLK 5                   // TM1637 CLK signal
#define DIO 6                   // TM1637 DIO signal
#define Tm 7                    // Time button (L=on)
#define St 8                    // Set button (L=on)

TM1637Display display(CLK, DIO);
uint8_t Data[] = { 0x3f, 0x3f, 0x3f, 0x3f };
int k;
const uint8_t SEG_OFF[] = {
  0b00111111,                   // O
  0b01110001,                   // F
  0b01110001,                   // F
  0b00000000
};
const uint8_t SEG_SdEr[] = {
  0b01101101,                   // S
  0b01011110,                   // d
  0b01111001,                   // E
  0b01010000                    // r
};

// for Real Time Clock
char  strYMDHMS[17];    // 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
int svTime[2] = {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);

  display.setBrightness(8);  // values are valid from 8 to 11
  for (k = 0; k < 4; k++) {
    Data[k] = 0x3f;
  }
  display.setSegments(Data);
  
  // when mode button holded at power-on
  if (digitalRead(Mdb) == LOW) {
    rtcSet();
  }
  // 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;
  }
  // set file namde GMxxx, where xxx is number
  while (1) {
    sprintf(fnumc, "%03d", fnum);
    fname[2] = fnumc[0];
    fname[3] = fnumc[1];
    fname[4] = fnumc[2];
    if  (!SD.exists(fname)) break;
    fnum++;
  }
  Serial.println(fname);
  gmLog = SD.open(fname, FILE_WRITE);
  gmLog.println("YYMMDD,hh:mm:ss,  CPM  , s-CPM ,uSv/h");
  Serial.println("YYMMDD,hh:mm:ss, CPM  , s-CPM ,uSv/h");

  // Set up CPM-to-nSv/h factors
  // 1) between point P 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=Cq*X
  Cq = (float)Yq / Xq;
  //Serial.print("A=");
  //Serial.print(A);
  //Serial.print(" B=");
  //Serial.print(B);
  //Serial.print(" Cq=");
  //Serial.println(Cq);

  // Set svTime at start of loop
  rRTC();                 // read RTC device
  cR2I();                 // convert RTC-format to Integers
  svTime[sS] = vI[mds];
  svTime[mM] = vI[mdm];
}

void loop()
{
  // 1) when GMT signal was detected
  if (digitalRead(Det) == LOW) {
    Cpm = Cpm + 1;
    if (Cpm >= CntV) {  // 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];
      svTime[sS] = vI[mds];   // save new time
      svTime[mM] = vI[mdm];
      if  (durS < 0) {
        durS = durS + 60;
        durM--;
      }
      if (durM < 0) {
        durM = durM + 60;
      }
      durS = durS + 60 * durM;
      float Cpmfx10 = (float)CntV * 600.0 / durS;
      // Cpm smoothing to sCPMx10
      sCPMx10 = (float)(sCPMx10 * sF/100.0 + Cpmfx10 * (100-sF)/100.0);
      if (sCPMx10 >= Xq) {
        Nsv = (float)(A * sCPMx10 - B );
        if (Nsv < 0) {
          Nsv = 0;
        }
        if (Nsv > 9999) {
          Nsv = 9999;
        }
      } else {
        Nsv = (float)(sCPMx10 * Cq);
      }
      CPMx10 = Cpmfx10;         // save CPM
      Cpm = 0;  // clear current Cpm
      // integers to characters
      ItoC();                 // Integers to Characters
      // Set LED digits
      int Su0 = Nsv / 1000;
      Data[0] = display.encodeDigit(Su0);
      int Su1 = (Nsv - Su0 * 1000) / 100;
      Data[1] = display.encodeDigit(Su1);
      int Su2 = (Nsv - Su0 * 1000 - Su1 * 100) / 10;
      Data[2] = display.encodeDigit(Su2);
      int Su3 = (Nsv - Su0 * 1000 - Su1 * 100 - Su2 * 10) % 10;
      Data[3] = display.encodeDigit(Su3);
      Data[0] = Data[0] + 0x80;       // insert dot
      display.setSegments(Data);
      // Write SD data
      String s = strYMDHMS;
      s.concat(",");
      strCPMx10[7] = strCPMx10[6];  // copy stopper
      strCPMx10[6] = strCPMx10[5];
      strCPMx10[5] = 0x2E;         // insert period
      s.concat(strCPMx10);
      s.concat(",");
      strsCPMx10[7] = strsCPMx10[6];  // copy stopper
      strsCPMx10[6] = strsCPMx10[5];
      strsCPMx10[5] = 0x2E;         // insert period
      s.concat(strsCPMx10);
      s.concat(",");
      for (k = 5; k >= 2; k--) {
        strNsv[k] = strNsv[k - 1];
      }
      strNsv[1] = 0x2E;           // insert period
      s.concat(strNsv);
      gmLog.println(s);
      Serial.println(s);
    }
    // elese consume time only to avoid double detection
    delayMicroseconds(80);
  }

  // 2) when Mode pushed to close SD card
  if (digitalRead(Mdb) == LOW) {
    gmLog.close();  // close the 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, "%02d%02d%02d,%02d:%02d:%02d", vI[mdY], vI[mdM], vI[mdD], vI[mdh], vI[mdm], vI[mds]);
  sprintf(strCPMx10, "%06d", CPMx10);
  sprintf(strsCPMx10, "%06d", sCPMx10);
  sprintf(strNsv, "%04d", Nsv);
}

// 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;
      // 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
    mD = mD - 1;
    if (mD == mdI) {
      mD = mdY;
    }
    // if (digitalRead(St) == LOW) break; // if Set pushed
  } // Repeat Set loop
  // out of Set loop
  while (digitalRead(St) == LOW) {} // until end of 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();
}

 全部で  300行ほどに収まっていますが、RTC-Writerなどの機能も含んでいますから、かなり小さいほうかと思います。

SDカード、TM1637ディスプレイ、I2CなどのライブラリーをIncludeしていますが、RTCは簡単なので自分でコーディング。これによりスケッチはArduino-Unoの容量の60%ですみ、グローバル変数は66%(ただし実装用はSerial.printをはずしますから少し小さい)となり余裕があります。

 

テスト後は、ATmega328pに書き込んで使いやすいブレッドボードにします。とはいってもDIP形式のATmega328が搭載された普通のUnoに書き込んで、そのICを使えばよいだけのことで、わざわざ書き込み器などを備えるよりも簡単です。28pin DIP の無圧(zero pressure)ソケットをUnoに予めセットしておけばAT-mega328の取付・取外しがとても楽です。例えば次のようにします。

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

手に入る28ピン無圧ソケットは半田付け用ですから、そのままでは足が短かったりします。そういう場合はその下に他のソケットで下駄をはかせます。下の例はまず平ピンソケットをはかせ、さらにその下に足がしっかりした丸ピンソケットをはかせた例です。無圧ソケットの足は直接丸ピンへは入りません。丸ピンソケットは14pin用を2つ使う方がセットしやすいです。

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

他のソケットで下駄をはかせた無圧(zero-pressure)ソケット

このようにしてプログラムを入れたATmega328pを使えば、ブレッドボード機は 下の写真のようにすっきりします。ATmega328pに外付けが必要なのは16MHzクリスタルオシレータと22pFキャパシターx2、それに適当な電源用キャパシターです。

 

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

この形ですと周囲の部品へ設定通りの電圧が供給できるので、消費電流等の測定にはこちらが適切です。また、他の測定もこの形が楽ですね。何を作るにも同じですが、ブレッドボードで動かす間は、熱を出さない周辺機器はビニールチャック袋に収めておくのが、思わぬトラブル防止に役立ちます。むき出しでは他と接触しますので。

 

PCB作成から実装までを、次回説明したいと思います。

 

前の記事:

a-tomi.hatenablog.com

a-tomi.hatenablog.com

a-tomi.hatenablog.com

a-tomi.hatenablog.com

次回の記事:

a-tomi.hatenablog.com

 

(c)2018 Akira Tominaga, All rights reserved.