Arduino jako měřič impedance

Odpovědět
Uživatelský avatar
JPLABS
Příspěvky: 62
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: 517
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();
  }
}

Odpovědět

Kdo je online

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