Arduino jako měřič impedance

Odpovědět
Uživatelský avatar
JPLABS
Příspěvky: 66
Registrován: 28 pro 2025, 16:52
Bydliště: Praha
Kontaktovat uživatele:

Arduino jako měřič impedance

Příspěvek od JPLABS » 24 kvě 2026, 17:48

Napadl mne jeden smysluplný projekt pro borce Arduinisty. Je to projekt, kde je málo vedlejšího hardware a hlavně to stojí a padá se software. Možno použít Arduino. Kromě Arduina potřebujete ještě:
3 kusy odporů
3 kusy kondenzátorů
3 kusy signálových diod
1 kus displej, doporučuju barevný grafický
1 kus kovové krabičky
2 kusy konektorů pro připojení napájení a pro připojení testované součástky.

To je všechno. Když vhodně a šikovně zapojíte ty tři odpory a tři kondenzátory a tři diody k Božskému Arduinu a sestavíte software pro Božské Arduino, můžete měřit impedanci obvodů a součástek a zobrazovat závislost impedance do grafů.
Kterýpak borec Arduinista toto dokáže vyřešit?
S časem se vše zhoršuje (zákon prof. Parkinsona)
Obrázek

Uživatelský avatar
Caster
Příspěvky: 518
Registrován: 11 zář 2019, 09:02

Re: Arduino jako měřič impedance

Příspěvek od Caster » 25 kvě 2026, 03:05

Schéma zapojení:

Obrázek

Arduino program:

Kód: Vybrat vše

/*
  Arduino Impedancni Analyzator v1.0.0
  Deska: Arduino UNO R4 Minima / UNO R4 WiFi (nutny skutecny DAC na A0)
  Displej: ILI9341 320x240 SPI, knihovny Adafruit_GFX + Adafruit_ILI9341

  Zapojeni, ktere tento program ocekava:
    A0/DAC OUT ---- VIN ---- RREF 1k ---- VOUT ---- Zx ---- GND
                          |             |
                         A1            A2

  TFT ILI9341:
    CS  -> D10
    DC  -> D9
    RST -> D8
    MOSI/SCK -> hardwarove SPI piny desky

  DULEZITE:
  - Vstupy A1 a A2 meri uzly VIN a VOUT. V merici ceste nesmi byt RC filtry,
    ktere by menily amplitudu nebo fazi signalu.
  - DAC vytvari kladne posunuty sinusovy signal; neni potreba seriovy vazebni
    kondenzator C1. Pokud ho pouzijete, je nutne doplnit spravne DC predpeti.
  - S jedinym referencnim odporem 1 kOhm je rozumny prakticky rozsah priblizne
    100 Ohm az 10 kOhm. Pro Ohmy az MOhmy je nutne prepinani RREF.

  Princip:
    I = (VIN - VOUT) / RREF
    Zx = VOUT / I = RREF * VOUT / (VIN - VOUT)

  Program snima komplexni zakladni harmonickou pomoci synchronni demodulace,
  takze zobrazuje modul |Z| i fazi impedance.
*/

#include <Arduino.h>
#include <SPI.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ILI9341.h>
#include <math.h>

#define PROGRAM_VERSION "v1.0.0"

// ------------------------------ Piny ---------------------------------------
static constexpr uint8_t PIN_DAC  = A0;
static constexpr uint8_t PIN_VIN  = A1;
static constexpr uint8_t PIN_VOUT = A2;

static constexpr uint8_t TFT_CS  = 10;
static constexpr uint8_t TFT_DC  = 9;
static constexpr uint8_t TFT_RST = 8;

Adafruit_ILI9341 tft(TFT_CS, TFT_DC, TFT_RST);

// --------------------------- Merici parametry ------------------------------
static constexpr float RREF_OHM = 1000.0f;
static constexpr uint16_t DAC_MID = 2048;       // 12bit DAC; stred signalu
static constexpr uint16_t DAC_AMPLITUDE = 1000; // bezpecne pod maximem 4095
static constexpr uint8_t SETTLE_CYCLES = 3;
static constexpr uint8_t MEASURE_CYCLES = 6;
static constexpr uint8_t MAX_POINTS_PER_CYCLE = 48;

static const float TEST_FREQUENCIES_HZ[] = {
  100.0f, 160.0f, 250.0f, 400.0f, 630.0f,
  1000.0f, 1600.0f, 2500.0f, 4000.0f
};
static constexpr uint8_t FREQ_COUNT = sizeof(TEST_FREQUENCIES_HZ) / sizeof(TEST_FREQUENCIES_HZ[0]);

// ------------------------------ Graf ---------------------------------------
static constexpr int16_t GRAPH_X = 42;
static constexpr int16_t GRAPH_Y = 49;
static constexpr int16_t GRAPH_W = 267;
static constexpr int16_t GRAPH_H = 130;
static constexpr float GRAPH_Z_MIN = 10.0f;
static constexpr float GRAPH_Z_MAX = 100000.0f;

struct ComplexValue {
  float re;
  float im;
};

struct Measurement {
  float frequencyHz;
  float magnitudeOhm;
  float phaseDeg;
  float vinMagnitude;
  float voutMagnitude;
  bool valid;
};

Measurement measurements[FREQ_COUNT];
bool continuousMode = true;
uint32_t lastSweepMs = 0;

// ------------------------- Komplexni matematika ----------------------------
ComplexValue complexSubtract(const ComplexValue &a, const ComplexValue &b) {
  return {a.re - b.re, a.im - b.im};
}

float complexMagnitude(const ComplexValue &a) {
  return sqrtf(a.re * a.re + a.im * a.im);
}

ComplexValue complexDivide(const ComplexValue &a, const ComplexValue &b) {
  const float d = b.re * b.re + b.im * b.im;
  if (d < 1.0e-12f) {
    return {NAN, NAN};
  }
  return {(a.re * b.re + a.im * b.im) / d,
          (a.im * b.re - a.re * b.im) / d};
}

// ----------------------------- Pomocne funkce ------------------------------
uint8_t pointsPerCycleForFrequency(float frequencyHz) {
  if (frequencyHz <= 1000.0f) return 48;
  if (frequencyHz <= 2500.0f) return 32;
  return 20;
}

uint16_t dacCodeForPhase(float phaseRad) {
  const float value = static_cast<float>(DAC_MID) + static_cast<float>(DAC_AMPLITUDE) * sinf(phaseRad);
  if (value < 0.0f) return 0;
  if (value > 4095.0f) return 4095;
  return static_cast<uint16_t>(value + 0.5f);
}

void waitUntilMicros(uint32_t targetUs) {
  while (static_cast<int32_t>(micros() - targetUs) < 0) {
    // Aktivni cekani zamerne: udrzuje pravidelnost buzeni a vzorkovani.
  }
}

void setDacMidpoint() {
  analogWrite(PIN_DAC, DAC_MID);
}

// ------------------------------- Mereni ------------------------------------
Measurement measureAtFrequency(float frequencyHz) {
  Measurement result{};
  result.frequencyHz = frequencyHz;
  result.valid = false;

  const uint8_t points = pointsPerCycleForFrequency(frequencyHz);
  const uint16_t samples = static_cast<uint16_t>(points) * MEASURE_CYCLES;
  const float stepPeriodUsF = 1000000.0f / (frequencyHz * static_cast<float>(points));
  const uint32_t stepPeriodUs = static_cast<uint32_t>(stepPeriodUsF + 0.5f);

  // Frekvence, kterou ve skutecnosti nastavime celočíselnym casovanim.
  const float actualFrequencyHz = 1000000.0f / (static_cast<float>(stepPeriodUs) * static_cast<float>(points));
  result.frequencyHz = actualFrequencyHz;

  uint32_t nextUs = micros() + 200;

  // Ustaleni obvodu pred vlastnim snimanim.
  for (uint16_t n = 0; n < static_cast<uint16_t>(points) * SETTLE_CYCLES; ++n) {
    const uint8_t k = n % points;
    const float phase = TWO_PI * static_cast<float>(k) / static_cast<float>(points);
    analogWrite(PIN_DAC, dacCodeForPhase(phase));
    nextUs += stepPeriodUs;
    waitUntilMicros(nextUs);
  }

  ComplexValue vin{0.0f, 0.0f};
  ComplexValue vout{0.0f, 0.0f};

  for (uint16_t n = 0; n < samples; ++n) {
    const uint8_t k = n % points;
    const float phase = TWO_PI * static_cast<float>(k) / static_cast<float>(points);
    const float c = cosf(phase);
    const float s = sinf(phase);

    analogWrite(PIN_DAC, dacCodeForPhase(phase));

    // Vzorek se odebere priblizne uprostred nastaveneho DAC kroku.
    waitUntilMicros(nextUs + stepPeriodUs / 2U);
    const float rawVin = static_cast<float>(analogRead(PIN_VIN));
    const float rawVout = static_cast<float>(analogRead(PIN_VOUT));

    vin.re += rawVin * c;
    vin.im -= rawVin * s;
    vout.re += rawVout * c;
    vout.im -= rawVout * s;

    nextUs += stepPeriodUs;
    waitUntilMicros(nextUs);
  }

  setDacMidpoint();

  const float scale = 2.0f / static_cast<float>(samples);
  vin.re *= scale;
  vin.im *= scale;
  vout.re *= scale;
  vout.im *= scale;

  const ComplexValue delta = complexSubtract(vin, vout);
  const float deltaMagnitude = complexMagnitude(delta);
  result.vinMagnitude = complexMagnitude(vin);
  result.voutMagnitude = complexMagnitude(vout);

  // Pri velmi malem napeti na RREF nelze spolehlive urcit proud.
  if (result.vinMagnitude < 2.0f || deltaMagnitude < 1.5f) {
    result.magnitudeOhm = NAN;
    result.phaseDeg = NAN;
    return result;
  }

  ComplexValue z = complexDivide(vout, delta);
  z.re *= RREF_OHM;
  z.im *= RREF_OHM;

  result.magnitudeOhm = complexMagnitude(z);
  result.phaseDeg = atan2f(z.im, z.re) * 180.0f / PI;
  result.valid = isfinite(result.magnitudeOhm) && isfinite(result.phaseDeg) && result.magnitudeOhm > 0.0f;
  return result;
}

// ------------------------------- Displej -----------------------------------
void drawHeader() {
  tft.fillScreen(ILI9341_BLACK);
  tft.setTextColor(ILI9341_CYAN);
  tft.setTextSize(2);
  tft.setCursor(6, 6);
  tft.print(F("IMPEDANCE |Z|"));

  tft.setTextColor(ILI9341_WHITE);
  tft.setTextSize(1);
  tft.setCursor(226, 10);
  tft.print(PROGRAM_VERSION);

  tft.setCursor(6, 28);
  tft.print(F("UNO R4 DAC A0 | VIN A1 | VOUT A2 | Rref=1k"));
}

int16_t graphYForMagnitude(float magnitudeOhm) {
  if (!isfinite(magnitudeOhm) || magnitudeOhm <= 0.0f) return GRAPH_Y + GRAPH_H;
  float z = magnitudeOhm;
  if (z < GRAPH_Z_MIN) z = GRAPH_Z_MIN;
  if (z > GRAPH_Z_MAX) z = GRAPH_Z_MAX;
  const float fraction = (log10f(z) - log10f(GRAPH_Z_MIN)) /
                         (log10f(GRAPH_Z_MAX) - log10f(GRAPH_Z_MIN));
  return GRAPH_Y + GRAPH_H - static_cast<int16_t>(fraction * GRAPH_H + 0.5f);
}

int16_t graphXForIndex(uint8_t index) {
  if (FREQ_COUNT <= 1) return GRAPH_X;
  return GRAPH_X + static_cast<int16_t>((static_cast<long>(index) * GRAPH_W) / (FREQ_COUNT - 1));
}

void drawGraphBackground() {
  tft.fillRect(0, 42, 320, 145, ILI9341_BLACK);
  tft.drawRect(GRAPH_X, GRAPH_Y, GRAPH_W, GRAPH_H, ILI9341_DARKGREY);

  const float labels[] = {10.0f, 100.0f, 1000.0f, 10000.0f, 100000.0f};
  const char *labelText[] = {"10", "100", "1k", "10k", "100k"};
  tft.setTextSize(1);
  for (uint8_t i = 0; i < 5; ++i) {
    const int16_t y = graphYForMagnitude(labels[i]);
    tft.drawFastHLine(GRAPH_X, y, GRAPH_W, ILI9341_DARKGREY);
    tft.setTextColor(ILI9341_WHITE);
    tft.setCursor(4, y - 3);
    tft.print(labelText[i]);
  }

  for (uint8_t i = 0; i < FREQ_COUNT; ++i) {
    const int16_t x = graphXForIndex(i);
    tft.drawFastVLine(x, GRAPH_Y, GRAPH_H, ILI9341_DARKGREY);
  }

  tft.setTextColor(ILI9341_WHITE);
  tft.setCursor(GRAPH_X, GRAPH_Y + GRAPH_H + 4);
  tft.print(F("100Hz"));
  tft.setCursor(GRAPH_X + 102, GRAPH_Y + GRAPH_H + 4);
  tft.print(F("1k"));
  tft.setCursor(GRAPH_X + GRAPH_W - 30, GRAPH_Y + GRAPH_H + 4);
  tft.print(F("4k"));
}

void displayLatestValues(const Measurement &m) {
  tft.fillRect(0, 195, 320, 45, ILI9341_BLACK);
  tft.setTextSize(1);
  tft.setTextColor(ILI9341_YELLOW);
  tft.setCursor(6, 198);
  tft.print(F("Posledni: "));
  tft.print(m.frequencyHz, 0);
  tft.print(F(" Hz"));

  tft.setCursor(6, 212);
  if (m.valid) {
    tft.setTextColor(ILI9341_GREEN);
    tft.print(F("|Z|="));
    if (m.magnitudeOhm >= 1000.0f) {
      tft.print(m.magnitudeOhm / 1000.0f, 2);
      tft.print(F(" kOhm"));
    } else {
      tft.print(m.magnitudeOhm, 1);
      tft.print(F(" Ohm"));
    }
    tft.setTextColor(ILI9341_MAGENTA);
    tft.setCursor(172, 212);
    tft.print(F("faze="));
    tft.print(m.phaseDeg, 1);
    tft.print(F(" deg"));
  } else {
    tft.setTextColor(ILI9341_RED);
    tft.print(F("MIMO ROZSAH / bez proudu"));
  }

  tft.setTextColor(ILI9341_WHITE);
  tft.setCursor(6, 227);
  tft.print(F("Serial: s=scan, c=continuous ON/OFF"));
}

void plotMeasurements() {
  drawGraphBackground();
  bool havePrevious = false;
  int16_t previousX = 0;
  int16_t previousY = 0;

  for (uint8_t i = 0; i < FREQ_COUNT; ++i) {
    if (!measurements[i].valid) {
      havePrevious = false;
      continue;
    }
    const int16_t x = graphXForIndex(i);
    const int16_t y = graphYForMagnitude(measurements[i].magnitudeOhm);
    if (havePrevious) {
      tft.drawLine(previousX, previousY, x, y, ILI9341_GREEN);
    }
    tft.fillCircle(x, y, 2, ILI9341_YELLOW);
    previousX = x;
    previousY = y;
    havePrevious = true;
  }
}

// ------------------------------- Sweep -------------------------------------
void runSweep() {
  tft.fillRect(0, 186, 320, 9, ILI9341_BLACK);
  tft.setTextSize(1);
  tft.setTextColor(ILI9341_CYAN);
  tft.setCursor(6, 187);
  tft.print(F("Merim..."));

  Serial.println();
  Serial.println(F("frequency_Hz;abs_Z_Ohm;phase_deg;Vin_DFT;Vout_DFT;status"));

  for (uint8_t i = 0; i < FREQ_COUNT; ++i) {
    measurements[i] = measureAtFrequency(TEST_FREQUENCIES_HZ[i]);
    const Measurement &m = measurements[i];

    Serial.print(m.frequencyHz, 2);
    Serial.print(';');
    if (m.valid) {
      Serial.print(m.magnitudeOhm, 3);
      Serial.print(';');
      Serial.print(m.phaseDeg, 3);
      Serial.print(';');
      Serial.print(m.vinMagnitude, 3);
      Serial.print(';');
      Serial.print(m.voutMagnitude, 3);
      Serial.println(F(";OK"));
    } else {
      Serial.print(F("NaN;NaN;"));
      Serial.print(m.vinMagnitude, 3);
      Serial.print(';');
      Serial.print(m.voutMagnitude, 3);
      Serial.println(F(";OUT_OF_RANGE"));
    }
  }

  plotMeasurements();
  displayLatestValues(measurements[FREQ_COUNT - 1]);

  tft.fillRect(0, 186, 320, 9, ILI9341_BLACK);
  tft.setTextColor(ILI9341_CYAN);
  tft.setCursor(6, 187);
  tft.print(continuousMode ? F("AUTO SWEEP") : F("MANUAL MODE"));

  lastSweepMs = millis();
}

// ------------------------------ Arduino API --------------------------------
void setup() {
  Serial.begin(115200);
  delay(300);

  analogWriteResolution(12);
  analogReadResolution(12);
  setDacMidpoint();

  tft.begin();
  tft.setRotation(1);
  drawHeader();
  drawGraphBackground();

  tft.setTextSize(1);
  tft.setTextColor(ILI9341_WHITE);
  tft.setCursor(6, 198);
  tft.print(F("Start mereni..."));

  Serial.println(F("Arduino Impedancni Analyzator " PROGRAM_VERSION));
  Serial.println(F("UNO R4: DAC=A0, VIN=A1, VOUT=A2, RREF=1000 Ohm"));
  Serial.println(F("Poznamka: s jednim RREF=1k je prakticky rozsah zhruba 100 Ohm az 10 kOhm."));
  Serial.println(F("Prikazy: s = jednorazovy sweep, c = prepnout automaticke mereni."));

  runSweep();
}

void loop() {
  if (Serial.available() > 0) {
    const char command = static_cast<char>(Serial.read());
    if (command == 's' || command == 'S') {
      runSweep();
    } else if (command == 'c' || command == 'C') {
      continuousMode = !continuousMode;
      Serial.print(F("Continuous mode: "));
      Serial.println(continuousMode ? F("ON") : F("OFF"));
      displayLatestValues(measurements[FREQ_COUNT - 1]);
    }
  }

  if (continuousMode && millis() - lastSweepMs >= 1500UL) {
    runSweep();
  }
}

Uživatelský avatar
JPLABS
Příspěvky: 66
Registrován: 28 pro 2025, 16:52
Bydliště: Praha
Kontaktovat uživatele:

Re: Arduino jako měřič impedance

Příspěvek od JPLABS » 29 kvě 2026, 00:29

koukal jsem na to jak jelen. Chvíli trvalo, než jsem se vzpamatoval.
Proboha, co to je zase za ptákovinu? Kdo je autorem?
Impedance má reálnou složku a imaginární složku. To co se v tom schematu měří nemá s impedancí nic společnýho. Z té rovnice se úplně vytratila frekvence. Krom toho, pro měření impedance pomocí voltmetrů jsou potřeba 3 střídavé (AC) voltmetry. Zde jsou jen dva. A to se ještě měří jakési napětí před a za referenčním odporem, který není nijak zapojen k měřené součástce. Jaký asi bude rozíl mezi napětím před a za referenčním odporem?
Co si mám představit pod tím "referenčním" odporem , které jsou zřejmě dva :lol: každý má 100 kOhm. Tedy nebudou úplně stejné. Jakou mají mít indukčnost? Ty odpory mají převážně reálnou složku, imaginární složka bude dost nízká. Jak můžou fungovat jako reference, když nejsou nijak zapojený k testovaný součástce? Ty diody tam akorát odříznou zápornou půlvlnu.
A co ten kondík 100nF paralelně k té testované součástce? Jaký má význam?
Jak se z toho měření vydedukuje imaginární a reálná složka?
Za další, zásadně se měří se vstupním a výstupním odporem 50 Ohm a pomocí čistého sinusového signálu, nikoliv schodovitého průběhu získaného z DDS nebo DA převodníku.
A nakonec, graf ise vykresluje do tzv. Shmithova diagramu https://en.wikipedia.org/wiki/Smith_chart.
S časem se vše zhoršuje (zákon prof. Parkinsona)
Obrázek

Odpovědět

Kdo je online

Uživatelé prohlížející si toto fórum: Žádní registrovaní uživatelé a 1 host