ESP32 (EP.7) ESP32 ทำเป็น RC meter

การใช้ ESP32 ทำเป็น RC meter …. ขอเริ่มที่การวัดค่าความต้านทานก่อน  

พื้นฐานที่ง่ายที่สุดและได้ผลดีมากพอสมควรก็คือการต่อตัวความต้านทานเป็นวงจรแบ่งแรงดัน (Voltage divider)  วงจรแบ่งแรงดันนั้นรายละเอียดของวงจรและสมการที่ใช้เป็นไปตามภาพด้านล่างนี้  

สมการที่ใช้จะไม่ยากเลยเนื่องจากมันก็เป็นกฎของโอห์มธรรมดานี่เองครับ  การประยุกต์ใช้วงจรแบ่งแรงดันเพื่อทำให้วัดค่าความต้านทานได้ก็คือ  ต่อ Vout ของวงจรแบ่งแรงดันกับ GPIO เพื่อวัดค่าแรงดัน  โดยมีแรงดัน Vin นำมาจากไฟ 3.3 โวลต์ของ ESP32 เอง  และ Vout ก็ต่อตรงกับ GPIO ของ ESP32 ใน ADC mode

  ในสมการล่างนี้  ตัวความต้านทานที่เราต้องการจะวัดค่าก็คือตัว R….. ส่วนตัว 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;
}