การใช้ ESP32 ทำเป็น RC meter …. ขอเริ่มที่การวัดค่าความต้านทานก่อน
พื้นฐานที่ง่ายที่สุดและได้ผลดีมากพอสมควรก็คือการต่อตัวความต้านทานเป็นวงจรแบ่งแรงดัน (Voltage divider) วงจรแบ่งแรงดันนั้นรายละเอียดของวงจรและสมการที่ใช้เป็นไปตามภาพด้านล่างนี้
สมการที่ใช้จะไม่ยากเลยเนื่องจากมันก็เป็นกฎของโอห์มธรรมดานี่เองครับ การประยุกต์ใช้วงจรแบ่งแรงดันเพื่อทำให้วัดค่าความต้านทานได้ก็คือ ต่อ Vout ของวงจรแบ่งแรงดันกับ GPIO เพื่อวัดค่าแรงดัน โดยมีแรงดัน Vin นำมาจากไฟ 3.3 โวลต์ของ ESP32 เอง และ Vout ก็ต่อตรงกับ GPIO ของ ESP32 ใน ADC mode

ในสมการล่างนี้ ตัวความต้านทานที่เราต้องการจะวัดค่าก็คือตัว R1 ….. ส่วนตัว R2 จะใช้ R ค่าคงที่ ซึ่งจากค่าแรงดัน input อ้างอิง 3.3 โวลต์ค่าของ R2 ที่เหมาะสมก็คือ 22kΩ …. ดังนั้นการหาสมการสุดท้ายที่จะใส่ใน code เราก็จะต้องย้ายข้างสมการ โดยในสมการให้แทนค่าแรงดัน Vin = 3.3 โวลต์และค่าเท่ากับ 22kΩ สุดท้ายแล้วจะได้สมการที่จะนำไปใส่ใน code ตามภาพด้านล่างนี้

ส่วนการวัดค่า Capacitor วิธีที่ง่ายที่สุดก็คือจะต้องต่อวงจรเพิ่มโดยใช้ IC 555 timer ครับ
จะขออธิบายถึงวงจรสำหรับ IC 555 timer ตัวนี้ก่อน (ภาพล่าง) การต่อ IC 555 ในลักษณะนี้จะเรียกว่า Astable multivibrator สูตรเพื่อหาความถี่จะมาจากค่าความสัมพันธ์ระหว่างอุปกรณ์ 3 ตัวก็คือ R1 , R2 และ C ซึ่งสูตรก็ง่ายมากรายละเอียดตามภาพล่าง
concept การทำงานก็คือ เมื่อเราจิ้ม capacito rที่ต้องการวัดลงไป ตัว IC 555 ก็จะกำเนิดความถี่ป้อนเข้า GPIO (digital mode) ของ ESP32 และวัดความถี่ + แปลงไปเป็นค่า Capacitor ที่เราจะวัด ในการ code ให้วัดค่า capacitor วิธีการที่ดีที่สุดก็คือใช้คุณสมบัติอันแม่นยำอย่างหนึ่งของ MCPWM โดยมันจะวัดคาบเวลาระหว่างของขาขึ้น (Rising edge) ของคลื่นความถี่ที่กำเนิดจาก IC 555 เข้ามาและคำนวณออกมาเป็นค่าของ capacitor เป็นไปตามสมการด้านล่างนี้

อุปกรณ์ทั้งหมดมีเพียงเท่านี้
จากภาพนี้ ในวงจรส่วนบนก็คือวงจรกำเนิดความถี่ของ IC 555 และสังเกตในจอภาพก็จะแสดงค่า Resistor , Capacitor

ในภาพล่างนี้ เอา capacitor ขนานกัน 2 ตัว (100 µF + 10 µF) ก็จะอ่านได้ค่า capacitance ที่ถูกต้องก็คือ 111.5 µF …. และใช้สาย jumper จิ้มวัด R 5.6kΩ ในจอภาพก็แสดงค่า 5.59 kΩ

Code ทั้งหมด
#include <Arduino.h>
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SH110X.h>
#include "driver/mcpwm_cap.h"
// Hardware Pin Definitions
#define CAP_INPUT_PIN 11 // Capacitance input (MCPWM)
#define RES_ANALOG_PIN 1 // Resistance input (ADC1_CH0 / GPIO 1)
#define I2C_SDA 40 // Shared OLED SDA
#define I2C_SCL 42 // Shared OLED SCL
#define SCREEN_ADDRESS 0x3C
// OLED Display Setup
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
Adafruit_SH1106G display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
// --- Capacitance Meter Constants & Variables ---
const double TIMER_CONSTANT = 1.44 / 3000.0; // 555 Timer Astable Mode (R1=1000, R2=1000)
const uint32_t APB_CLOCK = 80000000; // 80 MHz MCPWM clock source
volatile uint32_t last_capture_ticks = 0;
volatile uint32_t current_prescaler = 1;
volatile bool new_data_available = false;
volatile uint32_t last_interrupt_time = 0;
mcpwm_cap_channel_handle_t cap_chan = NULL;
mcpwm_cap_timer_handle_t cap_timer = NULL;
// Global variables to store latest measurements
double global_frequency = 0.0;
bool cap_connected = false;
// --- Resistance Meter Constants & Variables ---
unsigned long last_resistor_measure = 0;
const unsigned long resistor_interval = 500; // Measure resistance every 500ms
float global_resistance = -1.0; // -1.0 indicates "OPEN" or disconnected
// Forward Declarations
String formatWithDigits(long value);
void set_capture_prescaler(uint32_t prescale_val);
// --- Capacitance MCPWM ISR Callback ---
static bool IRAM_ATTR on_capture_callback(mcpwm_cap_channel_handle_t cap_chan, const mcpwm_capture_event_data_t *edata, void *user_data) {
static uint32_t last_trigger_count = 0;
static uint32_t pulse_counter = 0;
uint32_t current_count = edata->cap_value;
pulse_counter++;
if (pulse_counter >= current_prescaler) {
last_capture_ticks = current_count - last_trigger_count;
last_trigger_count = current_count;
pulse_counter = 0;
last_interrupt_time = millis();
new_data_available = true;
}
return false;
}
void set_capture_prescaler(uint32_t prescale_val) {
current_prescaler = prescale_val;
}
void setup() {
Serial.begin(115200);
// 1. Initialize shared I2C bus for the OLED display
Wire.begin(I2C_SDA, I2C_SCL);
if(!display.begin(SCREEN_ADDRESS, true)) {
Serial.println(F("SH1106 allocation failed"));
for(;;);
}
display.clearDisplay();
display.setTextColor(SH110X_WHITE);
display.setTextSize(1);
display.setCursor(0, 0);
display.println("Initializing Combo...");
display.display();
// 2. Configure Resistance ADC Input
analogReadResolution(12); // 0-4095
analogSetPinAttenuation(RES_ANALOG_PIN, ADC_11db); // ~0V to 3.1V - 3.3V
// 3. Initialize Capacitance MCPWM Capture Timer
mcpwm_capture_timer_config_t timer_config = {};
timer_config.group_id = 0;
timer_config.clk_src = MCPWM_CAPTURE_CLK_SRC_DEFAULT;
ESP_ERROR_CHECK(mcpwm_new_capture_timer(&timer_config, &cap_timer));
// 4. Initialize Capacitance MCPWM Capture Channel
mcpwm_capture_channel_config_t cap_chan_config = {};
cap_chan_config.gpio_num = CAP_INPUT_PIN;
cap_chan_config.prescale = 1;
cap_chan_config.flags.neg_edge = 0;
cap_chan_config.flags.pos_edge = 1;
cap_chan_config.flags.pull_up = 0;
cap_chan_config.flags.pull_down = 0;
ESP_ERROR_CHECK(mcpwm_new_capture_channel(cap_timer, &cap_chan_config, &cap_chan));
// 5. Register Capacitance ISR Callback
mcpwm_capture_event_callbacks_t cbs = {};
cbs.on_cap = on_capture_callback;
ESP_ERROR_CHECK(mcpwm_capture_channel_register_event_callbacks(cap_chan, &cbs, NULL));
// 6. Enable and Start Capacitance Hardware Peripherals
ESP_ERROR_CHECK(mcpwm_capture_channel_enable(cap_chan));
ESP_ERROR_CHECK(mcpwm_capture_timer_enable(cap_timer));
ESP_ERROR_CHECK(mcpwm_capture_timer_start(cap_timer));
last_interrupt_time = millis();
}
void loop() {
unsigned long currentMillis = millis();
// ==========================================
// 1. RESISTANCE MEASUREMENT LOGIC (Every 500ms)
// ==========================================
if (currentMillis - last_resistor_measure >= resistor_interval) {
last_resistor_measure = currentMillis;
int rawADC = analogRead(RES_ANALOG_PIN);
if (rawADC <= 10) {
global_resistance = -1.0; // Mark as OPEN circuit
} else {
float vGpio1 = (rawADC * 3.3f) / 4095.0f;
float resistance = (73480.0f / vGpio1) - 22000.0f;
if (resistance < 0.0f) resistance = 0.0f;
global_resistance = resistance;
}
}
// ==========================================
// 2. CAPACITANCE MEASUREMENT LOGIC (Asynchronous)
// ==========================================
// Check if pulse stream stopped (Timeout after 2.5 seconds)
if (millis() - last_interrupt_time > 2500) {
cap_connected = false;
global_frequency = 0.0;
set_capture_prescaler(1);
}
// New pulse batch caught by ISR
else if (new_data_available) {
new_data_available = false;
noInterrupts();
uint32_t ticks = last_capture_ticks;
uint32_t active_prescaler = current_prescaler;
interrupts();
if (ticks > 0) {
global_frequency = ((double)APB_CLOCK * (double)active_prescaler) / (double)ticks;
if (global_frequency > 600000.0) {
cap_connected = false;
} else {
cap_connected = true;
}
// Auto-Ranging Software Engine
if (ticks < 250 && active_prescaler == 1) {
set_capture_prescaler(16);
} else if (ticks > 50000 && active_prescaler == 16) {
set_capture_prescaler(1);
}
}
}
// ==========================================
// 3. UNIFIED OLED DISPLAY UPDATE (Every 300ms)
// ==========================================
static uint32_t last_display_update = 0;
if (millis() - last_display_update > 300) {
last_display_update = millis();
display.clearDisplay();
display.setTextColor(SH110X_WHITE);
// --- TOP HALF: RESISTANCE METER ---
display.setTextSize(1);
display.setCursor(0, 0);
display.print("RESISTANCE:");
display.setCursor(0, 10);
display.setTextSize(2);
if (global_resistance < 0.0f) {
display.print("OPEN");
} else if (global_resistance < 1000.0f) {
display.print(formatWithDigits((long)global_resistance));
display.print(" ");
display.print(" Ohm"); // If you just want it to say "Ohm" cleanly
} else {
float kOhm = global_resistance / 1000.0f;
display.print(kOhm, 2);
display.print(" K");
display.print("Ohm"); // If you just want it to say "Ohm" cleanly
}
// Dividers visually separates the two regions
display.drawLine(0, 30, 128, 30, SH110X_WHITE);
// --- BOTTOM HALF: CAPACITANCE METER ---
display.setTextSize(1);
display.setCursor(0, 34);
display.print("CAPACITANCE:");
if (!cap_connected) {
display.setCursor(0, 46);
display.setTextSize(2);
display.print("OPEN");
} else {
double capacitance_F = TIMER_CONSTANT / global_frequency;
double capacitance_uF = capacitance_F * 1000000.0;
display.setCursor(0, 46);
display.setTextSize(2);
if (capacitance_uF < 0.1) {
display.print(capacitance_uF, 4);
} else if (capacitance_uF < 10.0) {
display.print(capacitance_uF, 3);
} else {
display.print(capacitance_uF, 1);
}
display.setTextSize(1);
display.print(" uF");
}
display.display();
}
}
// Helper function to handle digit grouping (e.g., 22,000 instead of 22000)
String formatWithDigits(long value) {
String valStr = String(value);
String result = "";
int len = valStr.length();
for (int i = 0; i < len; i++) {
if (i > 0 && (len - i) % 3 == 0) {
result += ",";
}
result += valStr[i];
}
return result;
}