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;
}
}