2026-10-26 15:19:12 +04:00
|
|
|
|
/*
|
|
|
|
|
|
* DDS-генератор сигналов для лабораторных работ
|
2026-10-26 15:33:38 +04:00
|
|
|
|
* Платформа: RP2040 (Raspberry Pi Pico)
|
|
|
|
|
|
*
|
|
|
|
|
|
* Работает в ОБОИХ Arduino-ядрах:
|
2026-10-26 15:43:35 +04:00
|
|
|
|
* - earlephilhower (arduino-pico) — штатный ШИМ analogWriteFreq/analogWriteRange
|
|
|
|
|
|
* и таймер add_repeating_timer_us (pico-sdk);
|
|
|
|
|
|
* - официальное ядро "Raspberry Pi Pico/RP2040" (Arduino Mbed OS) — в пользовательском
|
|
|
|
|
|
* коде НЕТ ни analogWriteFreq, ни pico-sdk-заголовков (hardware/pwm.h, hardware/timer.h),
|
|
|
|
|
|
* поэтому здесь используется только mbed API, который является частью самого ядра:
|
|
|
|
|
|
* mbed::PwmOut (ШИМ) + mbed::Ticker (тактовый генератор выборок 62,5 кГц).
|
|
|
|
|
|
* Ветка выбирается автоматически по макросу ARDUINO_ARCH_MBED.
|
2026-10-26 15:19:12 +04:00
|
|
|
|
*
|
|
|
|
|
|
* ЧЕРНОВИК (AI-сгенерировано, 2026-10-26). Требует проверки преподавателем
|
|
|
|
|
|
* на реальном стенде: номиналы RC-фильтра, диапазоны частот, точность ADC.
|
|
|
|
|
|
*
|
|
|
|
|
|
* Возможности:
|
2026-10-26 15:33:38 +04:00
|
|
|
|
* - прямоугольник : аппаратный ШИМ (точная частота, независимая скважность);
|
|
|
|
|
|
* ниже 20 Гц — «программный» прямоугольник в таймерном ISR
|
|
|
|
|
|
* (аппаратный ШИМ RP2040 не опускается ниже ~7,5 Гц);
|
2026-10-26 15:19:12 +04:00
|
|
|
|
* - синус : таблица 256 точек + аккумулятор фазы (DDS) в таймерном
|
|
|
|
|
|
* ISR -> ШИМ (несущая = SAMPLE_HZ = 62,5 кГц) -> внешний
|
|
|
|
|
|
* ФНЧ (схема ниже); амплитуда — командой M;
|
|
|
|
|
|
* - свип : логарифмический (f0..f1, N точек) с синхроимпульсом
|
|
|
|
|
|
* на отдельном пине — для осциллографа/хост-скрипта;
|
|
|
|
|
|
* - измерение : ADC0 (размах за окно) — амплитуда на выходе
|
|
|
|
|
|
* исследуемой цепи (DUT) для автоматической АЧХ.
|
|
|
|
|
|
*
|
|
|
|
|
|
* ─── СХЕМА СИНУСА (обязательна: ШИМ даёт меандр, а не синус) ───
|
|
|
|
|
|
*
|
|
|
|
|
|
* GP15 ──[R1]──┬──[R2]──┬──────► OUT (вход DUT)
|
|
|
|
|
|
* │ │
|
|
|
|
|
|
* [C1] [C2]
|
|
|
|
|
|
* │ │
|
|
|
|
|
|
* GND GND
|
|
|
|
|
|
*
|
|
|
|
|
|
* Пассивный ФНЧ 2-го порядка. При несущей 62,5 кГц:
|
|
|
|
|
|
* R1 = R2 = 1 кОм, C1 = C2 = 47 нФ -> f_c ≈ 3,4 кГц
|
|
|
|
|
|
* подавление несущей ≈ (62,5/3,4)^2 ≈ 50 дБ -> пульсации ~10 мВ
|
|
|
|
|
|
* Номиналы для другой несущей: f_c и постоянную RC смотреть в README.
|
|
|
|
|
|
*
|
|
|
|
|
|
* ВНИМАНИЕ (важно): выходное сопротивление фильтра ~1 кОм. Вход
|
|
|
|
|
|
* интегратора — R1 = 10 кОм, получается делитель 10/11 (просадка ~9 %).
|
|
|
|
|
|
* Если это критично — поставить буфер-повторитель на ОУ (Sallen-Key):
|
|
|
|
|
|
* тот же фильтр 2-го порядка + низкое выходное сопротивление.
|
|
|
|
|
|
*
|
|
|
|
|
|
* Постоянная составляющая: при скважности 50 % среднее = 1,65 В, синус
|
|
|
|
|
|
* смещён на Uпит/2. Где смещение недопустимо — разделительный C = 1 мкФ
|
|
|
|
|
|
* последовательно + R = 47 кОм на землю (ФВЧ ≈ 3,4 Гц), либо уменьшить
|
|
|
|
|
|
* амплитуду командой M.
|
|
|
|
|
|
*
|
|
|
|
|
|
* Протокол команд (Serial, 115200, строка + \n):
|
|
|
|
|
|
* F <Гц> — установить частоту (1..500000)
|
|
|
|
|
|
* W Q | W S — режим: Q — прямоугольник, S — синус
|
|
|
|
|
|
* D <0..255> — скважность прямоугольника (128 = 50 %)
|
|
|
|
|
|
* M <0..127> — амплитуда синуса (127 = максимум)
|
|
|
|
|
|
* A — однократно измерить амплитуду на ADC0
|
|
|
|
|
|
* -> ответ "A <мВ размах>"
|
|
|
|
|
|
* SWEEP <f0> <f1> <n> <dwell_ms>
|
|
|
|
|
|
* — логарифмический свип; на каждой точке выдаёт
|
|
|
|
|
|
* "P <Гц> <мВ>" и импульс синхро на PIN_TRIG
|
|
|
|
|
|
* STOP — прервать свип
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
#include <Arduino.h>
|
|
|
|
|
|
#include <math.h>
|
2026-10-26 15:33:38 +04:00
|
|
|
|
|
2026-10-26 15:43:35 +04:00
|
|
|
|
// ───────────────── выбор бэкенда ─────────────────
|
|
|
|
|
|
// Официальное mbed-ядро: нет analogWriteFreq, нет pico-sdk в пользовательском
|
|
|
|
|
|
// коде -> только mbed API (PwmOut + Ticker). earlephilhower: analogWrite* +
|
|
|
|
|
|
// add_repeating_timer_us.
|
2026-10-26 15:33:38 +04:00
|
|
|
|
#if defined(ARDUINO_ARCH_MBED)
|
2026-10-26 15:43:35 +04:00
|
|
|
|
#define USE_MBED_API 1
|
|
|
|
|
|
#include "mbed.h"
|
2026-10-26 15:33:38 +04:00
|
|
|
|
#else
|
2026-10-26 15:43:35 +04:00
|
|
|
|
#define USE_MBED_API 0
|
|
|
|
|
|
#include "hardware/timer.h" // add_repeating_timer_us, repeating_timer_t
|
2026-10-26 15:33:38 +04:00
|
|
|
|
#endif
|
2026-10-26 15:19:12 +04:00
|
|
|
|
|
|
|
|
|
|
// ─────────────────────────── пины ───────────────────────────
|
|
|
|
|
|
#define PIN_OUT 15 // выход сигнала (прямоугольник — прямо; синус — через RC)
|
|
|
|
|
|
#define PIN_TRIG 14 // синхроимпульс (для осциллографа)
|
|
|
|
|
|
#define PIN_ADC 26 // ADC0 = A0 — вход измерения (выход DUT через делитель!)
|
|
|
|
|
|
|
|
|
|
|
|
// ─────────────────────── параметры DDS ──────────────────────
|
|
|
|
|
|
// ВАЖНО: несущая ШИМ и частота обновления выборок должны СОВПАДАТЬ.
|
|
|
|
|
|
// Значение скважности применяется один раз за период ШИМ; если обновлять
|
|
|
|
|
|
// выборки с другой частотой, появляется джиттер и биения на выходе фильтра.
|
|
|
|
|
|
// При 16 мкс период ISR занимает ~3 % CPU RP2040; при перегрузке —
|
|
|
|
|
|
// снизить до 31250 (тогда f_c фильтра пересчитать вниз пропорционально).
|
|
|
|
|
|
#define CARRIER_HZ 62500UL // несущая ШИМ для синуса (= частота выборок)
|
|
|
|
|
|
#define SAMPLE_HZ 62500UL // частота обновления выборок синуса
|
|
|
|
|
|
#define PWM_RANGE 255 // 8-бит (для синуса); прямоугольнику хватает
|
|
|
|
|
|
#define AMP_MAX 127 // максимальная амплитуда выборки (единиц duty)
|
|
|
|
|
|
|
2026-10-26 15:33:38 +04:00
|
|
|
|
// Прямоугольник ниже этой частоты генерится в ISR: аппаратный ШИМ RP2040
|
|
|
|
|
|
// (div 8.4 + wrap 16 бит) физически не опускается ниже ~7,5 Гц.
|
|
|
|
|
|
#define SW_SQ_MIN_HZ 20UL
|
|
|
|
|
|
|
2026-10-26 15:19:12 +04:00
|
|
|
|
// ───────────────────────── состояние ────────────────────────
|
|
|
|
|
|
enum WaveMode : uint8_t { MODE_SQUARE = 0, MODE_SINE = 1 };
|
|
|
|
|
|
|
|
|
|
|
|
static volatile uint32_t g_freq = 1000; // текущая частота, Гц
|
|
|
|
|
|
static volatile uint8_t g_duty = 128; // 0..255 (прямоугольник)
|
|
|
|
|
|
static volatile uint8_t g_amp = AMP_MAX;// амплитуда синуса, 0..AMP_MAX
|
|
|
|
|
|
static volatile WaveMode g_mode = MODE_SQUARE;
|
|
|
|
|
|
static volatile uint32_t g_phase = 0; // аккумулятор фазы (32 бит)
|
|
|
|
|
|
static volatile bool g_sweep = false; // флаг активного свипа
|
|
|
|
|
|
static int8_t g_lut[256]; // sin: -127..127
|
|
|
|
|
|
|
2026-10-26 15:33:38 +04:00
|
|
|
|
// ─────────────── универсальный слой ШИМ ───────────────
|
|
|
|
|
|
// pwmStart() — включить ШИМ на PIN_OUT (idempotent)
|
|
|
|
|
|
// pwmConfig(f, range) — настроить частоту несущей
|
|
|
|
|
|
// pwmDuty(d) — установить скважность (0..255, масштабируется на текущий wrap)
|
|
|
|
|
|
|
2026-10-26 15:43:35 +04:00
|
|
|
|
#if USE_MBED_API
|
|
|
|
|
|
// официальное mbed-ядро: PwmOut (16-бит размах внутри ядра)
|
|
|
|
|
|
static mbed::PwmOut g_pwm(digitalPinToPinName(PIN_OUT));
|
|
|
|
|
|
|
|
|
|
|
|
static void pwmStart() { /* PwmOut активен сразу */ }
|
2026-10-26 15:33:38 +04:00
|
|
|
|
static void pwmConfig(uint32_t freq_hz, uint32_t range) {
|
2026-10-26 15:43:35 +04:00
|
|
|
|
(void)range;
|
|
|
|
|
|
if (freq_hz < 1) freq_hz = 1;
|
|
|
|
|
|
g_pwm.period_us(1000000UL / freq_hz); // период в мкс (для 62,5 кГц = 16 мкс)
|
2026-10-26 15:33:38 +04:00
|
|
|
|
}
|
|
|
|
|
|
static void pwmDuty(uint32_t duty) {
|
2026-10-26 15:43:35 +04:00
|
|
|
|
if (duty > PWM_RANGE) duty = PWM_RANGE;
|
|
|
|
|
|
g_pwm.write((float)duty / (float)PWM_RANGE);
|
2026-10-26 15:33:38 +04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#else
|
2026-10-26 15:43:35 +04:00
|
|
|
|
// earlephilhower arduino-pico
|
|
|
|
|
|
static void pwmStart() { /* analogWrite сам включает ШИМ */ }
|
2026-10-26 15:33:38 +04:00
|
|
|
|
static void pwmConfig(uint32_t freq_hz, uint32_t range) {
|
2026-10-26 15:43:35 +04:00
|
|
|
|
analogWriteFreq(freq_hz);
|
|
|
|
|
|
analogWriteRange(range);
|
2026-10-26 15:33:38 +04:00
|
|
|
|
}
|
|
|
|
|
|
static void pwmDuty(uint32_t duty) {
|
2026-10-26 15:43:35 +04:00
|
|
|
|
analogWrite(PIN_OUT, duty);
|
2026-10-26 15:33:38 +04:00
|
|
|
|
}
|
2026-10-26 15:43:35 +04:00
|
|
|
|
#endif // USE_MBED_API
|
2026-10-26 15:33:38 +04:00
|
|
|
|
|
2026-10-26 15:19:12 +04:00
|
|
|
|
// ─────────────────────── таблица синуса ─────────────────────
|
|
|
|
|
|
static void buildLut() {
|
|
|
|
|
|
for (int i = 0; i < 256; ++i) {
|
|
|
|
|
|
g_lut[i] = (int8_t)lroundf(127.0f * sinf(2.0f * (float)M_PI * i / 256.0f));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-26 15:43:35 +04:00
|
|
|
|
// ─────────────── тело ISR: обновление выборок ────────────────
|
|
|
|
|
|
// В режиме SINE — DDS-шаг фазы и новая скважность каждые 16 мкс;
|
|
|
|
|
|
// в режиме SQUARE с f < SW_SQ_MIN_HZ — программный прямоугольник
|
|
|
|
|
|
// (тот же ШИМ, только скважность 255 или 0 по фазе);
|
|
|
|
|
|
// в режиме SQUARE с f >= SW_SQ_MIN_HZ ШИМ работает аппаратно, ISR молчит.
|
|
|
|
|
|
static void sampleIsrBody() {
|
2026-10-26 15:19:12 +04:00
|
|
|
|
if (g_mode == MODE_SINE) {
|
|
|
|
|
|
// шаг фазы = f * 2^32 / Fs (64-бит, чтобы не переполнить)
|
|
|
|
|
|
uint32_t step =
|
|
|
|
|
|
(uint32_t)((uint64_t)g_freq * (1ULL << 32) / SAMPLE_HZ);
|
|
|
|
|
|
g_phase += step;
|
|
|
|
|
|
uint8_t idx = (uint8_t)(g_phase >> 24);
|
|
|
|
|
|
// центрируем на 128 и масштабируем амплитудой (g_amp 0..127)
|
|
|
|
|
|
int32_t s = ((int32_t)g_lut[idx] * g_amp) / 127;
|
2026-10-26 15:33:38 +04:00
|
|
|
|
pwmDuty((uint8_t)(128 + s));
|
|
|
|
|
|
} else if (g_freq < SW_SQ_MIN_HZ) {
|
|
|
|
|
|
uint32_t step =
|
|
|
|
|
|
(uint32_t)((uint64_t)g_freq * (1ULL << 32) / SAMPLE_HZ);
|
|
|
|
|
|
g_phase += step;
|
2026-10-26 15:43:35 +04:00
|
|
|
|
pwmDuty(((g_phase >> 24) < g_duty) ? PWM_RANGE : 0);
|
2026-10-26 15:19:12 +04:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-26 15:43:35 +04:00
|
|
|
|
#if USE_MBED_API
|
|
|
|
|
|
static void sampleTick() { sampleIsrBody(); }
|
|
|
|
|
|
static mbed::Ticker g_tick;
|
|
|
|
|
|
static void startSampleClock() {
|
|
|
|
|
|
// 16 мкс -> 62,5 кГц; Ticker — аппаратный таймер mbed
|
|
|
|
|
|
g_tick.attach_us(&sampleTick, 1000000L / SAMPLE_HZ);
|
|
|
|
|
|
}
|
|
|
|
|
|
#else
|
|
|
|
|
|
static bool onSample(repeating_timer_t *) {
|
|
|
|
|
|
sampleIsrBody();
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
2026-10-26 15:19:12 +04:00
|
|
|
|
static repeating_timer_t g_timer;
|
2026-10-26 15:43:35 +04:00
|
|
|
|
static void startSampleClock() {
|
|
|
|
|
|
add_repeating_timer_us(-(1000000L / SAMPLE_HZ), onSample, nullptr, &g_timer);
|
|
|
|
|
|
}
|
|
|
|
|
|
#endif
|
2026-10-26 15:19:12 +04:00
|
|
|
|
|
|
|
|
|
|
// ─────────────────── применить режим/частоту ────────────────
|
|
|
|
|
|
static void applySquare() {
|
2026-10-26 15:33:38 +04:00
|
|
|
|
if (g_freq < SW_SQ_MIN_HZ) {
|
2026-10-26 15:43:35 +04:00
|
|
|
|
// программный прямоугольник (ISR): несущая = SAMPLE_HZ
|
|
|
|
|
|
pwmStart();
|
|
|
|
|
|
pwmConfig(SAMPLE_HZ, PWM_RANGE);
|
|
|
|
|
|
pwmDuty(g_duty > 0 ? PWM_RANGE : 0);
|
2026-10-26 15:33:38 +04:00
|
|
|
|
} else {
|
|
|
|
|
|
pwmStart();
|
|
|
|
|
|
pwmConfig(g_freq, PWM_RANGE);
|
|
|
|
|
|
pwmDuty(g_duty);
|
|
|
|
|
|
}
|
2026-10-26 15:19:12 +04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void applySine() {
|
|
|
|
|
|
// частота ШИМ = SAMPLE_HZ (совпадает с ISR), размах — 8 бит
|
2026-10-26 15:33:38 +04:00
|
|
|
|
pwmStart();
|
|
|
|
|
|
pwmConfig(SAMPLE_HZ, PWM_RANGE);
|
|
|
|
|
|
// уровень выборок обновляет ISR
|
2026-10-26 15:19:12 +04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void setFrequency(uint32_t hz) {
|
|
|
|
|
|
if (hz < 1) hz = 1;
|
|
|
|
|
|
if (hz > 500000) hz = 500000;
|
|
|
|
|
|
g_freq = hz;
|
|
|
|
|
|
if (g_mode == MODE_SQUARE) applySquare();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void setMode(WaveMode m) {
|
|
|
|
|
|
g_mode = m;
|
|
|
|
|
|
if (m == MODE_SQUARE) applySquare();
|
|
|
|
|
|
else applySine();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ─────────────────── измерение амплитуды (ADC) ──────────────
|
|
|
|
|
|
// Возвращает размах (пик-пик) в милливольтах за окно window_ms.
|
|
|
|
|
|
// TODO (студенту): заменить окно по времени на окно по числу периодов
|
|
|
|
|
|
// измеряемой частоты, чтобы размах считался по целому числу периодов.
|
|
|
|
|
|
static uint32_t measureAmplitudeMv(uint32_t window_ms = 50) {
|
|
|
|
|
|
uint16_t vmin = 4095, vmax = 0;
|
|
|
|
|
|
uint32_t t0 = millis();
|
|
|
|
|
|
while ((millis() - t0) < window_ms) {
|
|
|
|
|
|
uint16_t v = analogRead(PIN_ADC);
|
|
|
|
|
|
if (v < vmin) vmin = v;
|
|
|
|
|
|
if (v > vmax) vmax = v;
|
|
|
|
|
|
}
|
|
|
|
|
|
// ADC 12 бит, Vref = 3,3 В
|
|
|
|
|
|
return (uint32_t)((uint32_t)(vmax - vmin) * 3300UL / 4095UL);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ───────────────────────── свип ─────────────────────────────
|
|
|
|
|
|
static void triggerPulse() {
|
|
|
|
|
|
digitalWrite(PIN_TRIG, HIGH);
|
|
|
|
|
|
delayMicroseconds(50);
|
|
|
|
|
|
digitalWrite(PIN_TRIG, LOW);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void runSweep(float f0, float f1, int n, uint32_t dwell_ms) {
|
|
|
|
|
|
if (n < 2) n = 2;
|
|
|
|
|
|
if (f0 < 1) f0 = 1;
|
|
|
|
|
|
if (f1 <= f0) f1 = f0 * 10.0f;
|
|
|
|
|
|
g_sweep = true;
|
|
|
|
|
|
|
|
|
|
|
|
const float ratio = powf(f1 / f0, 1.0f / (float)(n - 1));
|
|
|
|
|
|
float f = f0;
|
|
|
|
|
|
for (int i = 0; i < n && g_sweep; ++i) {
|
|
|
|
|
|
setFrequency((uint32_t)lroundf(f));
|
|
|
|
|
|
triggerPulse(); // метка точки для осциллографа
|
|
|
|
|
|
delay(dwell_ms); // дать DUT/фильтру устояться
|
|
|
|
|
|
uint32_t mv = measureAmplitudeMv();
|
|
|
|
|
|
Serial.print("P "); // точка АЧХ: частота + размах
|
|
|
|
|
|
Serial.print((uint32_t)lroundf(f));
|
|
|
|
|
|
Serial.print(' ');
|
|
|
|
|
|
Serial.println(mv);
|
|
|
|
|
|
f *= ratio;
|
|
|
|
|
|
}
|
|
|
|
|
|
Serial.println("DONE");
|
|
|
|
|
|
setFrequency((uint32_t)f0); // вернуться к началу
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ─────────────────────── разбор команд ──────────────────────
|
|
|
|
|
|
static void handleLine(char *line) {
|
|
|
|
|
|
char *cmd = strtok(line, " \t");
|
|
|
|
|
|
if (!cmd) return;
|
|
|
|
|
|
|
|
|
|
|
|
if (!strcasecmp(cmd, "F")) {
|
|
|
|
|
|
char *a = strtok(nullptr, " \t");
|
|
|
|
|
|
if (a) setFrequency((uint32_t)strtoul(a, nullptr, 10));
|
|
|
|
|
|
Serial.print("OK F "); Serial.println(g_freq);
|
|
|
|
|
|
} else if (!strcasecmp(cmd, "W")) {
|
|
|
|
|
|
char *a = strtok(nullptr, " \t");
|
|
|
|
|
|
if (a) setMode((!strcasecmp(a, "S")) ? MODE_SINE : MODE_SQUARE);
|
|
|
|
|
|
Serial.print("OK W "); Serial.println(g_mode == MODE_SINE ? "S" : "Q");
|
|
|
|
|
|
} else if (!strcasecmp(cmd, "D")) {
|
|
|
|
|
|
char *a = strtok(nullptr, " \t");
|
|
|
|
|
|
if (a) { g_duty = (uint8_t)constrain(atoi(a), 0, 255); applySquare(); }
|
|
|
|
|
|
Serial.print("OK D "); Serial.println(g_duty);
|
|
|
|
|
|
} else if (!strcasecmp(cmd, "M")) {
|
|
|
|
|
|
char *a = strtok(nullptr, " \t");
|
|
|
|
|
|
if (a) g_amp = (uint8_t)constrain(atoi(a), 0, AMP_MAX);
|
|
|
|
|
|
Serial.print("OK M "); Serial.println(g_amp);
|
|
|
|
|
|
} else if (!strcasecmp(cmd, "A")) {
|
|
|
|
|
|
Serial.print("A "); Serial.println(measureAmplitudeMv());
|
|
|
|
|
|
} else if (!strcasecmp(cmd, "SWEEP")) {
|
|
|
|
|
|
char *a0 = strtok(nullptr, " \t");
|
|
|
|
|
|
char *a1 = strtok(nullptr, " \t");
|
|
|
|
|
|
char *a2 = strtok(nullptr, " \t");
|
|
|
|
|
|
char *a3 = strtok(nullptr, " \t");
|
|
|
|
|
|
if (a0 && a1 && a2 && a3) {
|
|
|
|
|
|
runSweep(atof(a0), atof(a1), atoi(a2), strtoul(a3, nullptr, 10));
|
|
|
|
|
|
}
|
|
|
|
|
|
} else if (!strcasecmp(cmd, "STOP")) {
|
|
|
|
|
|
g_sweep = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ────────────────────────── setup/loop ──────────────────────
|
|
|
|
|
|
void setup() {
|
|
|
|
|
|
Serial.begin(115200);
|
|
|
|
|
|
buildLut();
|
|
|
|
|
|
pinMode(PIN_TRIG, OUTPUT);
|
|
|
|
|
|
digitalWrite(PIN_TRIG, LOW);
|
|
|
|
|
|
analogReadResolution(12); // ADC0 — 12 бит
|
2026-10-26 15:43:35 +04:00
|
|
|
|
startSampleClock();
|
2026-10-26 15:19:12 +04:00
|
|
|
|
setMode(MODE_SQUARE);
|
|
|
|
|
|
setFrequency(1000);
|
|
|
|
|
|
Serial.println("# DDS generator ready (RP2040). help: F/W/D/A/SWEEP/STOP");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void loop() {
|
|
|
|
|
|
static char buf[96];
|
|
|
|
|
|
static size_t len = 0;
|
|
|
|
|
|
while (Serial.available()) {
|
|
|
|
|
|
char c = (char)Serial.read();
|
|
|
|
|
|
if (c == '\n' || c == '\r') {
|
|
|
|
|
|
buf[len] = '\0';
|
|
|
|
|
|
if (len) handleLine(buf);
|
|
|
|
|
|
len = 0;
|
|
|
|
|
|
} else if (len < sizeof(buf) - 1) {
|
|
|
|
|
|
buf[len++] = c;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|