nobcha23の日記

PICマイコンやArduinoを使う電子回路遊びを紹介します

R909-tester基板にオシロ機能を移植してみた

周波数カウンタの移植ができたので、気をよくしてsiliconvalley4066さんのオシロ機能を移植してみようかと思います。ATmega328P向け、ESP32向けに加えRasberry Pi Pico zeroまで網羅されているので、ありがたいです。siliconvalley4066さんに感謝します。

ss1.xrea.com

 

さて、移植の際の課題はSW割付です。オリジナルはスイッチ4つなんですが、R909-tester基板ではロータリーエンコーダーと2個のスイッチにしないといけません。そこで、パラメータ増減のRight/Leftスイッチ処理をロータリーエンコーダに入れ替えることにします。周波数カウンタ移植の際にはREサービスをLOOP中のセンス方式したので、いまいち応答がかったるかったのを思い出しました。そこで、今回は割り込みにします。

 

スイッチ類のポート入れ替え、CheckSW()関数の中でパラメータ増減の処理をREに置き換えで意外とスムーズに動き出しました。OLEDに画面表示が出て、SWを操作すると反応するので、デバッグもWYSIWYGで進みます。

github.com

 

github.com

PCBパタン設計ミスでOLED用のi2cピンヘッダが番号順が逆です。


と言っても、まだ完全ではなくて、まだデバッグ中です。信号入力ポートも入れ替えたんですが、そっちがうまくいきません。まあ、でもほぼ8割は動いたので、次はどうするかと考えています。ATmega328Pの時は新しい機能を移植するときは新機能と引き換えにメモリ圧縮するのが大変でしたが、この切手サイズCPUモジュールになってからそんな心配がなくなったのがうれしいですね。

 

R909-tester基板を製作、組み立ててみた


さて、Rasberry Pi Pico zeroを用いたR909-testerと言う試作を行っています。今回PCBWAYのプロモーション協力の勧誘があったので応募し、基板を作成してもらいました。なのでブログでPCBWAYさんの紹介をしておきます。

お勧め基板メーカ

設計はいつものKiCADで設計し、PCBWayにガーバーファイルを送り、2日で基板完成、約一週間で到着しました。二層基板でHASL仕上げで、パターン、スルーホール、シルクなどの精度にも問題は全くありません。

 

使用する部品はRasberry Pi Pico zero、OLED、Rotary-Encoder、LED、スイッチ、ピンヘッダとソケット多数、更にチップ部品のR/C/Di、アルミ電解コンです。部品表は次です。

github.com

 

そして、組立てたんですが、早速バグが見つかりました。OLED用に用意したi2cコネクタの裏表実装指示ミスでした。そこで、デバッグを進めるためワイヤー長10cmぐらいのハーネスで変換ケーブルを作成しつないだら、それでも表示が出ません。おかしいなとOLEDテストスケッチを書いて、i2cスキャンを入れたら、アドレスが見つかりません。ケーブルやめて直接接続したら、画面出ました。プルアップが10kΩなのがまずかったですね。AIにお伺い立てたら、2.2kΩぐらいにしなさいと言われてしまいました。

今回デバッグのために情報寄せ集めで作ったOLED表示テストスケッチは次です。

基板と組立デバッグ中の回路

 

これでやっと、基板が動作するようになり、R909-testerスケッチの高精度レシプロカル周波数カウンタバージョンを動かしました。すると、ブロッキングが影響し、ロータリーエンコーダー(割り込みでない)が効かないことが判りました。測定下限周波数を10Hzにするなら、GATE_TIMEは100mSで良いので、ブロッキング時間が少なくなります。それでもREの応答が鈍いですが、頻繁に選択するものではないと考え、この設定で行くことにしました。

 

この基板で動作するスケッチとしてはPIO版の周波数カウンタを搭載した版と、

github.com


レシプロカルカウンタ搭載の版です。レシプロカル版では入力端子をライブラリのデフォールトに合わせるためGPIO7になっています。

github.com


また、プリント基板のPCBWay価格ですが、2026年7月の時点で基板代金5USD,OCS輸送費8USDでした。

The PCB was produced with the cooperation of PCBWay.

R909-testerに高精度周波数カウンタをマージできました

Rasberry Pi Pico zeroを利用するお手軽実験工具としてR909-testerというものを試作しております。で、どんなものなのかというと、導電ブザー、Cメータ、ダイオード判定、周波数カウンタです。勢いで買ったRasberry Pi Pico zeroモジュール基板の使い道探しで始めましたが、そこそこ面白い展開です。周波数カウンタとして、GITHUBで紹介されていたPIOでカウント、ソフト時間関数でゲートタイムをいうスケッチを移植しました。

 

50MHzぐらいまで計測できたのでこれはすごいぞと思いましたが、ソフトウェアでのゲートなので、数百ヘルツの変動があります。

 

そこで、もっと精度が良いのがないかということで、siliconvalley4066さんのFreqMeasureRPPを移植しようとしました。ところが、なかなかうまくいきませんでした。AIに手伝ってもらったのが裏目に出たようです。そこで、自分で見直し、手を入れたら動き始めました。50MHzの水晶発振回路の出力が数Hzも変動しないので、なかなか気持ちが良いです。siliconvalley4066さんありがとうございました。

 

 

50MHz水晶発振回路信号を観測

また、この回路をR909シリーズの受信機とかVFOなどと同じ制御パネル寸法で基板のパターン設計中です。Rasberry Pi Pico zeroモジュールにはLi+電池の3.0-4.2Vを3.3V電源仕様にしてくれるLDOが搭載されているので、Li+電池にTP4066基板をくっつけた電源でR909シリーズを動作させられると期待しております。

 

 

Raspberry Pi Pico zeroをArduinoで制御し、ロータリーエンコーダで機能選択し、OLED表示します。使ったRPPはWaveshare社の設計コピー品のようですので、IDEでのCPU選択はWaveshare RP2040 PiZeroと言うのを選びます。なぜか時々IDEとRPP間のシリアル接続が外れたりします。そんな時にはRPPのResetボタンBootボタンを同時押しし、Resetを先に外して、WindowsがUF2メモリとして認識するようにします。RPPのローダ無しでUSBメモリーへダウンロードするという方法です。なおArduino IDEはV2.3.8を使ってます。


今回の 試作に関する、回路図、スケッチ、基板パターン図(今後の予定)などはGITHUBにアップロードしますので、ご活用ください。

試作のまとめページは次になります。R909-tester

 

Raspberry Pi Pico Zeroを使ったR909-Tester(7) -- ダイオード判別

大した機能ではありません。順方向電圧を測り、ダイオードの種類を判定するというものです。

C測定端子を流用します。、

GPIO14 → HIGH出力にし100kΩ直列
被測定ダイオードはGPIO27で電圧測定

 

測定電流は概算で

Siダイオード:約(3.3-0.7)/100k ≒ 26μA
LED赤:約15μA
LED青:約5~10μA

この電流だとLEDはほぼ点灯しませんが、順方向電圧測定には使えるはずです。

 

ADCから求めた順方向電圧 Vf を利用し次の判定を行います。
「ダイオード判定画面」

赤LED判定

判定テーブル案

Vf        判定
0~0.05        SHORT
0.05~0.35V    Schottky/Ge
0.35~0.90V    Silicon Diode
0.90~2.00V    Red LED
2.00~2.50V    Green LED
2.50~3.20V    Blue/White LED
>3.20V    Open


この機能は測定と表示だけでしたので、簡単に実装できました。以上でR909-teaterの試作実験は終わりますが、折角なので、プリント基板にしようかと思っています。基板サイズは従来のPANEL制御部と同じ寸法にし、RF基板とのIFピンヘッダも用意し、R909-DSPシリーズ受信機の制御部にも使えるようにしようかと思っています。

 

PCB

Raspberry Pi Pico zeroをArduinoで制御し、ロータリーエンコーダで機能選択し、OLED表示するというR909-testerなる試作回路を作りました。使ったRPPはWaveshare社の設計コピー品のようですので、IDEでのCPU選択はWaveshare RP2040 zeroと言うのを選びました。なぜか時々IDEとRPP間のシリアル接続が外れたりします。そんな時にはRPPのResetボタンBootボタンを同時押しし、Resetを先に外して、WindowsがUF2メモリとして認識するようにし、RPPのローダ無しでUSBメモリーへダウンロードするという道を選ぶときがありました。Arduino IDEはV2.3.8を使ってます。


今回の 試作に関する、回路図、スケッチ、基板パターン図(今後の予定)などはGITHUBにアップロードしますので、ご活用ください。

試作のまとめページは次になります。R909-tester

 

 

R909-Tester 第6報 -- ステートマシンでスケッチを整理する

1.はじめに

R909-Testerは当初、Raspberry Pi Pico zeroを使いこなそうと、導通テスターとしてスタートしました。その後、コンデンサメータ、周波数カウンタ、ダイオード判別機能などの機能を追加しています。

 

 

機能を増やすに際し、「現在どの測定モードなのか」「測定モードの中で何の処理中なのか」を管理する必要を感じました。

そして、R909-Testerの制御スケッチにステートマシンを採用しました。その流れについて紹介します。

2.複合機能回路の悩み

単機能の装置であれば、

loop()
{
    continuityTest();
}

だけで、他に気を使う必要はありませんが、複数機能になると、

if(mode==0)
{
    continuityTest();
}
else if(mode==1)
{
    capacitorMeter();
}
else if(mode==2)
{
    frequencyCounter();
}

のように分岐が増え、エンコーダ回転以外でもスイッチ短押し、長押しなどの操作を加えたくなります。そうすると管理構造が複雑になります。

 

3.状態管理を導入

3.1 mode定義

そこでR909-Testerでは、まずは「何の測定しているか」を状態(mode)として定義することにしました。

CONTINUITY
CAPACITOR
COUNTER
DIODE

の4つをmodeに当てはめ、現在の状態を変数で保持するようにします。

enum Mode
{
  CONTINUITY,
  CAPACITOR,
  COUNTER,
  DIODE
};

3.2 状態遷移

ロータリーエンコーダを回すと、

CONTINUITY
 ↓
CAPACITOR
 ↓
COUNTER
 ↓
DIODE
 ↓
CONTINUITY

と順番に移動し、状態遷移します。

 

3.3 実装例

LOOPで回っているとき、現在状態に応じ処理を切り替えます。

switch(currentMode)
{
  case CONTINUITY:
    continuityTest();
    break;

  case CAPACITOR:
    capacitorMeter();
    break;

  case COUNTER:
    frequencyCounter();
    break;

  case DIODE:
    diodeChecker();
    break;
}

新しい機能をスケッチに追加するときも、Modeを一つ増やすだけで済みますから、機能出し入れ修正するときに作業工数低減効果は大きいです。

 

3.4 Mode内でState管理を導入

次に機能の中でもCAPACITORとCOUNTERは「測定パラメータ選択」「測定結果待ち」「結果表示」と処理状態が時間経過で存在するので、状態管理必要です。ここでState管理を導入します。

状態遷移はどうなっているのかというと、例えば、ModeでCAPACITORが選ばれると次のように遷移し、結果を得られ、表示に至ります。

// --- CAPACITANCE state  ---
enum CapState {
  CAP_IDLE,
  CAP_DISCHARGE,
  CAP_DISCHARGE_WAIT,
  CAP_CHARGE,
  CAP_CHARGE_WAIT,
  CAP_CALC,
  CAP_RESULT
};

 

CAP_STATE

従来ならdelay()を使い、時間合わせしながら処理するところです。そうすると、delay()が走っている間、ロータリーエンコーダは応答できず、応答が緩慢になるなど操作性を悪くしていました。応答良くないストレスが解消しました。このR909-testerでは複数タスクの並列処理は行っていませんが、今後この管理方式の採用で並列処理に対応できる下地も出きました。

 

3.5 効果
R909-Testerは小規模な回路ですが、それでもステートマシンを導入したことで、動作シーケンスの見通しが良くなり、機能追加が容易になり、結果的にバグが減り、デバッグしやすくなったという効果がありました。

将来的には、

設定メニュー
EEPROM保存
校正機能

なども追加できるだろうと考えています。

 

4. おわりに

電子工作では回路設計と試作でいつも精一杯ですが、機能が増えるとソフトウェア構造も重要になり、スケッチの大規模化とか、コードの流用性などを意識した構造も考えないといけません。

今回R909-Testerではステートマシンを採用したことで、複数の測定機能を整理し実装しやすくなりました。FrequencyCounterPicoも容易に取り込むことができました。

スケッチ、回路図、基板、組立説明などのデータは GITHUBやHPに用意する予定です。

chitose6thplant.fc2.page

 

次回はこの構造のおかげで、簡単に追加できたダイオード判別機能について紹介する予定です。

 

 

 

Raspberry Pi Pico Zeroを使ったR909-Tester(5)― 周波数カウンタを追加

RP2040 Zeroを使ったR909-Testerですが、まずは導通テスター機能を追加しました。

次にコンデンサ容量測定機能を組み込みました。

R909-tester機能ブロック図

そして、次々と機能を組み込むためにステートマシンにし、周波数カウンタ機能を追加したのが今回の投稿です。

 

PIO利用で、Raspberry Pi Pico Zeroを使用する周波数カウンタのスケッチについてはsilliconvalley4066さんの投稿を参考にしました。今回はステートマシン対応したので、引用スケッチの中まで入り込まずコピペで周波数カウンタ(PIO利用)のスケッチを組み込むことができました。ステートマシン構造については追って説明します。

すごいCPUパワーですね。Raspberry Pi Pico Zeroをほとんど裸で使う周波数カウンタですが、なんと50MHzぐらいまで応答します。

50MHzをカウントできた



 

R909-Tester開発はまだまだ続きます。残っているのはダイオード判定機能です。この機能はちょっと簡単すぎるかも、ダイオードの順方向電圧を測ろうというちゃちな機能です。ショットキーダイオード、一般整流用ダイオード、LED各色で異なる電圧を測ります。次回はR909-testerシリーズで機能追加の支えになっているステートマシン構造についてどうなっているかを説明しておこうかと思います。

もともとは、使い道を失ったRP2040 Zero基板の活用策から始まりましがが、いつの間にか便利な実験プラットフォームに変身してきます。


データ類保存用にGITHUBを用意しました。R909-tester 

また、ホームページにまとめ記事を載せました。

Raspberry Pi Pico Zeroを使ったR909-Tester(4)― Arduihoあるあるだったようです

RP2040 Zeroを使ったR909-Testerへの追加機能のC測定で、繰り返すと二回目以降の値が小さくなるという不具合を追求しました。

今回の件で興味深いのは、
「GPIO27をADC入力からデジタル出力へ切り替えて放電する方式で、大容量コンデンサだけ問題が発生した」ことです。ところが、更にオシロで見てみたらどのコンデンサでも放電しきれてませんでした。放電待ち時間がたまたま500mSだったので、5μF以上で引っかかっていただけです。

アナログ設定から、デジタルにして放電しようとしてもできないのは、RP2040の内部回路なのか、Arduinoライブラリの癖なのか、あるいは中華RP2040 Zero基板特有の問題なのか・・。チャッピーさんに調べてもらたらったら、Raspberry Pi Pico ZeroではGPIO26-28の扱いは注意を要するとのこと。アナログからデジタルへのダイナミック切り替えがうまくいかないらしい、結論はアナログかデジタルかどちらかの設定で使うべしとの事らしいです。

DeepSeekさんにも聞いてみたら、Pinmode()関数とか、analogRead()関数による設定値が尾を引くようだから低レベル関数のgpio_set_dir(CAP_SENSE_PIN, GPIO_OUT);などを使うようにしたらどうかとのこと。教えてもらったようにしましたが、結局はダメでした。

ということで、対策ですが、実用上はダイオード1本(GPIO27からGPIO14に1T4ダイオードをつなぐ)で解決してしまいました。この辺りは”Arduinoあるある”として深追いせず、ひとまず次のテーマへ進みたいと思います。chatGPTチャッピーさんからは面白いネタだからもっと調べましょうよと煽られましたが、また今度にします。

>

R909-testerシリーズ(導通+Cメータ)
<今のところの回路図です。

ところで、測定した「106」表示のMLCCらしきCは6.33μF程度しかありませんでした。以前フランクリン式LCメータでも5μF程度しか測定できなかった記憶がありますので、こちらは測定器よりもコンデンサ側の問題でしょう。MLCCの謎は別途追求してみようかと思います。

ここまできて、このCメータ機能は102から106(1000PF~10μF)ぐらいは測れるということです。小さい値はRP2040の関数が間に合わない、大きい方は充電時間が長くなってしまいます。

R909-Tester開発はまだまだ続きます。
次はダイオード判定機能か、周波数カウンタ機能か。その前に今回delay()待ちでロータリーエンコーダが応答しなくなったのが、気にかかります。そこで、R909-DSPスケッチ開発の時も適用したようにステートマシン化による処理の整理を行おうかと思います。

使い道を失ったRP2040 Zero基板が、いつの間にか便利な実験プラットフォームに変身してきます。


今回は導通計、Cメータまでのスケッチを添付しておきます。

// ============================================================
// R909_tester_C_RPP__ST_20260618.ino  Version 0.7

//  2026.06.18   kpa radio  by nobcha
//
// GPIO26 OPEN:0.3V, SHORT:0.03mA,(MAX:0.3mA)
// GPIO27 target C - 100kohm GPIO14 switch 1000pF-10uF
//
// State machine
//
// loop()
//   ├ measureCONT()
//   ├ measureC()
//   ├ measureDIODE()
//   ├ measurePARAMCO()
//   ├ measurePARAMC() 
//   └ measurCOUNT()
//
// Pin assignment:
//   TESTER:
//     ADC_PIN  ----> GP26
//     3.3V--10k --├--TERM/A--SAMPLE--TERM/B--GND
//                 ├--------1k--------------GND
//    
//   C-meter --> GP27
//            ├- 100kohm ---- GPIO14 -- GND
//            ├- 1T4 diode ├  
//   Rotary encoder:
//     ENC_A -> GP2
//     ENC_B -> GP1
//     ENC_SW-> GP3
//   OLED I2C:
//     SDA   -> GP4
//     SCL   -> GP5
//   Buzzer:
//     Tone  -> GP7
//
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET     -1
#define SCREEN_ADDRESS 0x3C

Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

// ==========================================
// PIN
// ==========================================
const int ENCODER_A  = 2;  // A
const int ENCODER_B  = 1;  // B
const int ENCODER_SW = 3;  // SW
const int ADC_PIN    = 26; // ADC
const int BUZZER_PIN = 7;  // Ceramic Speaker connected to GPIO 7
const int CAP_CHARGE_PIN = 14;
const int CAP_SENSE_PIN  = 27;
// Conduction meter

// Capacitor meter
const float CAP_R = 100000.0; // 100kΩ
const float RREF = 10000.0; // 10kOhm
unsigned long lastMeasureTime;
#define MEASURE_INTERVAL 200

// ==========================================
// The structure must be set on top.
// ==========================================
struct Measurement {
  int adc_val;
  float Rp;
  float x;
};
// ==========================================
// Measurement data
// ==========================================
float capValue = -1.0;
int lastADC = 0;

// --- MENU mode  ---
enum Mode {
  MODE_CONT,   // CONT CHECK
  MODE_C,      // Capacitor(Future)
  MODE_DIODE,  // Diode pole(Future)
  MODE_PARAM,  // Parameter check(For CO debugging)
  MODE_PARAMC, // Parameter check(For CCapO debugging)
  MODE_COUNT
};
int currentMode = MODE_CONT;
int lastClkState;

// --- CAPACITANCE state  ---
enum CapState {
  CAP_IDLE,
  CAP_DISCHARGE,
  CAP_DISCHARGE_WAIT,
  CAP_CHARGE,
  CAP_CHARGE_WAIT,
  CAP_CALC,
  CAP_RESULT
};

CapState capState = CAP_IDLE;

unsigned long capTimer = 0;
unsigned long capStartTime = 0;
unsigned long capElapsed = 0;

// --- prototyping ---
Measurement doMeasure();        // Structure
void displayModeCont(float rx); // Fixed to match definition
void displayModeC();
void displayModeDiode();
void displayModeParamCO(Measurement m);
float calculateTrueXBasedOnRealParameters(float rp); 
float measureCapacitance();
void displayModeParamC();
void serviceCapMeter();
bool isContMode();
bool isCapMode();

//
void setup() {
  Wire.setSDA(4);     // Set SDA to GPIO 4
  Wire.setSCL(5);     // Set SCL to GPIO 5
  Wire.begin();       // SDA:4, SCL:5

  // To set RP2040's ADC resolution as 12bit(0-4095)
  analogReadResolution(12);

  // Initialise PINs
  pinMode(ENCODER_A, INPUT_PULLUP);
  pinMode(ENCODER_B, INPUT_PULLUP);
  pinMode(ENCODER_SW, INPUT_PULLUP);
  
  // ADC
  pinMode(ADC_PIN, INPUT); 
  pinMode(BUZZER_PIN, OUTPUT);
  digitalWrite(BUZZER_PIN, LOW);

  // C meter
  pinMode(CAP_CHARGE_PIN, OUTPUT);
  digitalWrite(CAP_CHARGE_PIN, LOW);
  pinMode(CAP_SENSE_PIN, INPUT);

  lastClkState = digitalRead(ENCODER_A);

  if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
    for(;;);
  }
  
  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(SSD1306_WHITE);
  display.setCursor(0, 0);
  display.println("R909-TESTER v0.7");
  display.display();
  delay(1000);
}

// ---  Calculating Rp and x ---
Measurement doMeasure() {
  Measurement m;
  uint32_t sum = 0;
  for(int i=0; i<4; i++) {
    sum += analogRead(ADC_PIN);
  }
  m.adc_val = sum / 4;

  if (m.adc_val < 5) {
    m.Rp = 0.0;
    m.x = 0.0;
    return m;
  }
  if (m.adc_val > 4090) {
    m.Rp = -1.0;
    m.x = -1.0;
    return m;
  }

  // 1. At first calculating combined Rp
  m.Rp = (float)m.adc_val * RREF / (4095.0 - (float)m.adc_val);

  // 2. Substructing 1000Ohm and get x
  m.x = calculateTrueXBasedOnRealParameters(m.Rp);
  
  return m;
}

void loop() {
// loop controle  

  // Sense rotary encoder position (Polled out of the 150ms timer to avoid missing steps)
  int clkState = digitalRead(ENCODER_A);

  serviceCapMeter();

// 
  if (clkState == LOW && lastClkState == HIGH) {
    if (digitalRead(ENCODER_B) != clkState) {
      currentMode++;
      if (currentMode >= MODE_COUNT) currentMode = 0;
    } else {
      currentMode--;
      if (currentMode < 0) currentMode = MODE_COUNT - 1;
    }
  }
  lastClkState = clkState;

  // Mode management
  static int oldMode = -1;

  if(oldMode != currentMode) {
    bool oldCapMode =
      (oldMode == MODE_C ||
       oldMode == MODE_PARAMC);

    bool newCapMode =
      (currentMode == MODE_C ||
       currentMode == MODE_PARAMC);

    if(!oldCapMode && newCapMode) {
        capState = CAP_DISCHARGE;
    }

    if(oldCapMode && !newCapMode) {
        capState = CAP_IDLE;
    }

    oldMode = currentMode;
  }

  // 200ms cyclic display
  unsigned long currentTime = millis();
  if (currentTime - lastMeasureTime >= MEASURE_INTERVAL) {
    lastMeasureTime = currentTime;

    noTone(BUZZER_PIN);
    // Logic decision
    Measurement m = doMeasure();

    display.clearDisplay();
    display.setTextColor(SSD1306_WHITE);

    // mode selection
    switch (currentMode) {
      case MODE_CONT:
        displayModeCont(m.x);
        break;

      case MODE_PARAM:
        displayModeParamCO(m);
        break;

      case MODE_C:
        displayModeC();
        break;

      case MODE_PARAMC:
        displayModeParamC();
        break;
      
      case MODE_DIODE:
        displayModeDiode();
        break;
    }

    static uint32_t dispTimer=0;

    if(millis()-dispTimer > 400) {
      dispTimer=millis();
      display.display();
    }
  }
}

bool isContMode() {
    return (currentMode == MODE_CONT ||
            currentMode == MODE_PARAM);
}


bool isCapMode() {
    return (currentMode == MODE_C ||
          currentMode == MODE_PARAMC);
}


// --- Open/Short checker ---
void displayModeCont(float rx) {
  display.setTextSize(1);  display.setCursor(0, 0);  display.print("[ MODE SELECT ]");
  display.setTextSize(2);  display.setCursor(0, 18);
  display.println("1.CONT CHK");
  display.setCursor(32, 36);
  display.setTextSize(3);

  int current_adc = analogRead(ADC_PIN);
  
  // If ADC is lower than30(GND)SHORT
  if (current_adc >= 0 && current_adc < 30) {
    display.println("SHORT");
    tone(BUZZER_PIN, 2000 + 10 * current_adc);
  } else { 
    display.println("OPEN"); 
    noTone(BUZZER_PIN);
  }
}

// --- capacitor ---
void displayModeC() {

  display.setTextSize(1);
  display.setCursor(0,0);
  display.println("[ MODE SELECT ]");

  display.setTextSize(1);
  display.setCursor(0,18);
  display.println("2.CAPACITOR");
  display.setTextSize(2);
  display.setCursor(0,42);

  if(capValue < 0) {
    display.println("OPEN");
    return;
  }

  if(capValue < 1e-9) {

    display.print(capValue * 1e12,1);
    display.print(" pF");

  } else if(capValue < 1e-6) {

    display.print(capValue * 1e9,1);
    display.print(" nF");

  } else {

    display.print(capValue * 1e6,2);
    display.print(" uF");
  }
}

// --- diode ---
void displayModeDiode() {
  display.setTextSize(1);  display.setCursor(0, 0);  display.print("[ MODE SELECT ]");
  display.setTextSize(2);  display.setCursor(0, 18); display.println("3.DIODE");
}

// --- Display internal parameters ---
void displayModeParamCO(Measurement m) {
  display.setTextSize(1);
  display.setCursor(0, 0);
  display.print("[4.PARAMETER MONITOR]");
  
  display.setCursor(0, 15);
  display.print("RAW ADC: ");
  display.println(m.adc_val);
  
  display.setCursor(0, 26);
  display.print("V(pin) : "); display.print((float)m.adc_val * 3.3 / 4095.0, 2); display.println(" V");
  
  display.setCursor(0, 33);
  display.print("Calc Rp: ");
  if(m.Rp < 0) {
    display.println("OVER/OPEN");
  } else {
    display.print(m.Rp, 1); display.println(" Ohm"); 
  }
  
  display.setCursor(0, 47);
  display.print("True X  : "); 
  if(m.x < 0) {
    display.println("OPEN");
  } else {
    display.print(m.x, 1); display.println(" Ohm");
  }
}

// --- Display internal parameters ---
void displayModeParamC() {
  display.setTextSize(1);
  display.setCursor(0, 0);
  display.print("[5.C PARAMETER MON]");
  
  display.setCursor(0, 15);
  display.print("STATE=");
  display.println((int)capState);
  
  display.setCursor(0, 26);
  display.print("ADC=");
  display.println(lastADC);

  display.setCursor(0, 37);
  display.print("ELAPSED=");
  display.println(capElapsed);

  display.print("CAP=");
  display.println(capValue,9);
}

float calculateTrueXBasedOnRealParameters(float rp) {
  if (rp < 1000.0 && rp >= 0) {
    return (1000.0 * rp) / (1000.0 - rp);
  }
  return -1.0;
}

float measureCapacitance() {

  const float R_CHARGE = 100000.0;   // 100kΩ

  // discharge
  //  pinMode(CAP_SENSE_PIN, INPUT);
  // delay(10);
  digitalWrite(CAP_CHARGE_PIN, LOW);
  // pinMode(CAP_SENSE_PIN, OUTPUT);
  // digitalWrite(CAP_SENSE_PIN, LOW);

  // (500);

  //  pinMode(CAP_SENSE_PIN, INPUT);

  // charge start

  unsigned long startTime = micros();

  digitalWrite(CAP_CHARGE_PIN, HIGH);

  while ((lastADC = analogRead(CAP_SENSE_PIN)) < 2588) {

    if ((micros() - startTime) > 3000000UL) {
      return -1.0;
    }
  }

  capElapsed = micros() - startTime;
  digitalWrite(CAP_CHARGE_PIN, LOW);
  float capacitance =
    ((float)capElapsed * 1.0e-6) / R_CHARGE;

  return capacitance;
}

// Managing Capacitance meter state
void serviceCapMeter() {
  switch(capState) {
    case CAP_IDLE:
      break;

    case CAP_DISCHARGE:
      digitalWrite(CAP_CHARGE_PIN, LOW);
      capTimer = millis();
      capState = CAP_DISCHARGE_WAIT;
      break;

    case CAP_DISCHARGE_WAIT:
      if(millis() - capTimer > 100) {
        capState = CAP_CHARGE;
      }
      break;

    case CAP_CHARGE:
      digitalWrite(CAP_CHARGE_PIN, HIGH);
      capStartTime = micros();
      capState = CAP_CHARGE_WAIT;
      break;

    case CAP_CHARGE_WAIT:
      lastADC = analogRead(CAP_SENSE_PIN);
      if(lastADC >= 2588) {
        capElapsed = micros() - capStartTime;
        digitalWrite(CAP_CHARGE_PIN, LOW);
        capState = CAP_CALC;
      }

      if((micros() - capStartTime) > 3000000UL) {
        capValue = -1.0;
        digitalWrite(CAP_CHARGE_PIN, LOW);
        capState = CAP_RESULT;
      }
      break;

    case CAP_CALC:
      capValue =
          ((float)capElapsed * 1.0e-6f) / 100000.0f;

      capTimer = millis();
      capState = CAP_RESULT;
      break;

    case CAP_RESULT:
      if(millis() - capTimer > 1000) {
        capState = CAP_DISCHARGE;
      }
      break;
  }
}