Oscilloscope art

https://www.youtube.com/watch?v=lrtXt2Liaf8 以前看到就很想來實驗一下,最近蠻有空的,要說示波器的話,我想先買簡單版的順便三用電表也不見了,買了這台zt702s https://www.youtube.com/shorts/EBNega43BBY 那主控端就是nodemcu 了,那這邊用了node mcu 的兩個adc 和螢幕和旋鈕 再來就是比較有趣的震鏡可以控制xy軸這時候再買一個雷射當光源好材料到齊了開組

c
/*
  GALVO – Auto-cycle Waveforms (Square ▸ Triangle ▸ Circle)
  ---------------------------------------------------------
  • DAC25 → X  • DAC26 → Y
  • ADC34 旋鈕:1-60 fps
  • 每 2 秒自動切換圖樣:Square → Triangle → Circle → …
*/

#include <Arduino.h>

// ── 腳位 ─────────────────────────────────────
constexpr int DAC_X     = 25;          // 內建 DAC1
constexpr int DAC_Y     = 26;          // 內建 DAC2
constexpr int ADC_KNOB  = 34;          // 旋鈕

// ── 參數 ────────────────────────────────────
constexpr int MAX_POINTS = 512;        // 每幀點數
constexpr int FPS_MIN = 1, FPS_MAX = 60;
constexpr uint8_t AMP = 100;           // 圖形半徑 (0-255)

// ── 波形列舉 ────────────────────────────────
enum class Pattern { Square, Triangle, Circle };
Pattern current = Pattern::Square;

uint8_t xBuf[MAX_POINTS], yBuf[MAX_POINTS];
volatile int idx = 0;

hw_timer_t *timer = nullptr;

/* ──────────────────────────────────────────
   各圖形生成器
   ──────────────────────────────────────────*/
void buildSquare()
{
  int seg = MAX_POINTS / 4;            // 每邊點數
  for (int i = 0; i < MAX_POINTS; ++i) {
    int p = i % seg;
    if (i < seg) {                     // 右 → 上
      xBuf[i] = 127 + AMP;
      yBuf[i] = 127 + AMP - (2 * AMP * p) / seg;
    } else if (i < 2 * seg) {          // 上 → 左
      xBuf[i] = 127 + AMP - (2 * AMP * p) / seg;
      yBuf[i] = 127 - AMP;
    } else if (i < 3 * seg) {          // 左 → 下
      xBuf[i] = 127 - AMP;
      yBuf[i] = 127 - AMP + (2 * AMP * p) / seg;
    } else {                           // 下 → 右
      xBuf[i] = 127 - AMP + (2 * AMP * p) / seg;
      yBuf[i] = 127 + AMP;
    }
  }
}
/* ──────────────────────────────────────────
   正三角 (頂點在上方、底邊水平)
   ──────────────────────────────────────────*/
void buildTriangle()
{
  const uint8_t x1 = 127;        // 頂:正中
  const uint8_t y1 = 127 - AMP;

  const uint8_t x2 = 127 + AMP;  // 右下
  const uint8_t y2 = 127 + AMP;

  const uint8_t x3 = 127 - AMP;  // 左下
  const uint8_t y3 = 127 + AMP;

  int seg = MAX_POINTS / 3;      // 三條邊各 seg 點

  for (int i = 0; i < MAX_POINTS; ++i) {
    int k   = i / seg;           // 第幾條邊 0,1,2
    int idx = i % seg;           // 該邊內位置
    float t = float(idx) / (seg - 1);

    switch (k) {
      case 0:   // 頂 → 右下
        xBuf[i] = uint8_t( x1 + (x2 - x1) * t );
        yBuf[i] = uint8_t( y1 + (y2 - y1) * t );
        break;

      case 1:   // 右下 → 左下
        xBuf[i] = uint8_t( x2 + (x3 - x2) * t );
        yBuf[i] = y2;            // y 固定
        break;

      default:  // 左下 → 頂
        xBuf[i] = uint8_t( x3 + (x1 - x3) * t );
        yBuf[i] = uint8_t( y3 + (y1 - y3) * t );
        break;
    }
  }
}

void buildCircle()
{
  for (int i = 0; i < MAX_POINTS; ++i) {
    float a = 2.0f * PI * i / MAX_POINTS;
    xBuf[i] = 127 + AMP * sinf(a);
    yBuf[i] = 127 + AMP * cosf(a);
  }
}

/* 選圖形並重建表格 */
void rebuild()
{
  switch (current) {
    case Pattern::Square:   buildSquare();   break;
    case Pattern::Triangle: buildTriangle(); break;
    case Pattern::Circle:   buildCircle();   break;
  }
}

/* ── Timer 相關 ─────────────────────────── */
void IRAM_ATTR onTimer()
{
  dacWrite(DAC_X, xBuf[idx]);
  dacWrite(DAC_Y, yBuf[idx]);
  idx = (idx + 1) % MAX_POINTS;
}

void setFPS(int fps)
{
  uint64_t step = 1'000'000ULL / (fps * MAX_POINTS);
  if (step < 10) step = 10;            // ≈100 kHz 上限

  if (timer) { timerEnd(timer); timer = nullptr; }
  timer = timerBegin(1'000'000);       // 1 MHz → 1 µs
  timerAttachInterrupt(timer, &onTimer);
  timerAlarm(timer, step, true, 0);
}

/* ── 初始化 ─────────────────────────────── */
void setup()
{
  analogReadResolution(12);
  rebuild();          // 先建 Square
  setFPS(10);         // 預設 10 fps
}

/* ── 主迴圈 ─────────────────────────────── */
void loop()
{
  static uint32_t lastSw = 0;          // 圖形切換計時
  static uint32_t lastFPS = 0;         // FPS 更新計時

  uint32_t now = millis();

  /* 每 2 秒自動切換圖形 */
  if (now - lastSw >= 2000) {
    current = (current == Pattern::Square)   ? Pattern::Triangle :
              (current == Pattern::Triangle) ? Pattern::Circle   :
                                               Pattern::Square;
    rebuild();
    lastSw = now;
  }

  /* 每 0.2 秒讀旋鈕,更新 FPS */
  if (now - lastFPS >= 200) {
    int adc = analogRead(ADC_KNOB);
    int fps = map(adc, 0, 4095, FPS_MIN, FPS_MAX);
    fps = constrain(fps, FPS_MIN, FPS_MAX);
    setFPS(fps);
    lastFPS = now;
  }
}

image 注意線要插110v可能會有危險,不確定中國會不會爆炸

image image image image 如果再針對 雷射模組做開關應該就可以畫出流暢的圖