From 7860b44290f221e8fdb50e0194e637fb1a568326 Mon Sep 17 00:00:00 2001 From: mss Date: Tue, 27 Oct 2026 20:46:04 +0400 Subject: [PATCH] =?UTF-8?q?ai:=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=2004-dds-pio-rp2040.ino=20(PIO+DMA=20=D0=BD=D0=B0?= =?UTF-8?q?=20Arduino,=20=D1=8F=D0=B4=D1=80=D0=BE=20earlephilhower);=20tas?= =?UTF-8?q?k.md=20=D0=B8=20=D0=B1=D1=80=D0=BE=D1=88=D1=8E=D1=80=D0=B0=20?= =?UTF-8?q?=D1=81=D0=B8=D0=BD=D1=85=D1=80=D0=BE=D0=BD=D0=B8=D0=B7=D0=B8?= =?UTF-8?q?=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D1=8B=20=D1=81=20=D0=A0=D0=9F?= =?UTF-8?q?=D0=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...peredatochnaya-funkciya-na-ou-student.docx | Bin 43493 -> 43658 bytes stub/04-dds-pio-rp2040.ino | 618 ++++++++++++++++++ task.md | 10 +- 3 files changed, 625 insertions(+), 3 deletions(-) create mode 100644 stub/04-dds-pio-rp2040.ino diff --git a/print/methodical-lab5-peredatochnaya-funkciya-na-ou-student.docx b/print/methodical-lab5-peredatochnaya-funkciya-na-ou-student.docx index 85ed0228909eac0a558bad0264f2f52ed1f31db1..4726d6842d939455fb5b1449f5cb52b18b18e825 100644 GIT binary patch delta 8217 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 O*`pE{*{Sqj<^KVPR6!5` diff --git a/stub/04-dds-pio-rp2040.ino b/stub/04-dds-pio-rp2040.ino new file mode 100644 index 0000000..f777159 --- /dev/null +++ b/stub/04-dds-pio-rp2040.ino @@ -0,0 +1,618 @@ +/* + * DDS-генератор сигналов на PIO (RP2040) — вариант для Arduino IDE + * + * ЧЕРНОВИК. Требует проверки преподавателем на реальном стенде. + * + * ВАЖНО: этот скетч требует ядра **earlephilhower (arduino-pico)**. + * В официальном mbed-ядре pico-sdk скрыт от пользовательского кода, + * и PIO/DMA недоступны. В шапке это указано не случайно: на mbed-ядре + * скетч не соберётся. + * Установка ядра: Boards Manager -> "Raspberry Pi Pico/RP2040" + * (github.com/earlephilhower/arduino-pico) + * + * ─── ЧЕМ ОТЛИЧАЕТСЯ ОТ 01-dds-generator-rp2040.ino ─── + * + * 01-...ino формирует синус программно: каждый такт CPU (в прерывании) + * считает очередную выборку и пишет её в ШИМ. Чем выше несущая, тем + * больше нагрузка на CPU, и частота выборок ограничена. + * + * Здесь сигнал формирует аппаратный блок PIO, а данные ему подаёт DMA. + * CPU один раз считает буфер и больше не участвует. Программа PIO — + * всего две инструкции: + * + * pull block ; взять 32-битное слово из FIFO (через DMA) + * out pins, BITS ; вывести BITS младших бит слова в порт + * + * ─── О ФОРМЕ СИГНАЛА: ЗАДАЁТСЯ В КОДЕ, А НЕ КОМАНДОЙ ─── + * + * В C++ нет разбора выражений «на лету» (как команда FP в CircuitPython). + * Форма задаётся функцией waveform(t) ниже — студент меняет её и + * ПЕРЕКОМПИЛИРУЕТ скетч. Это надёжнее и проще: никакого eval. + * + * t — «время внутри периода»: 0 в начале, 1 в конце, + * шаг 1/m, где m = N*OVERSAMPLE (число точек буфера). + * Функция возвращает значение в диапазоне [-1, 1]. + * + * Примеры (раскомментировать нужную): + * + * return sinf(2.0f * M_PI * t); // чистый синус + * return 0.3f*sinf(2*M_PI*t) + 0.5f*sinf(4*M_PI*t); // сумма гармоник + * return 4.0f * fabsf(t - 0.5f) - 1.0f; // треугольник + * return 2.0f * t - 1.0f; // пила + * return (t < 0.5f) ? 1.0f : -1.0f; // меандр + * + * ─── ТОЧНОСТЬ УРОВНЯ: ДЕЛЬТА-СИГМА ─── + * + * Выход меняется только на +1/-1/0 за шаг, поэтому СОСЕДНИЕ ЗНАЧЕНИЯ + * буфера всегда отличаются не более чем на 1 (никаких скачков). + * Дробную точность даёт чередование соседних кодов, которое усредняет + * внешний ФНЧ: число различимых уровней = (2^BITS - 1) * m + 1. + * + * БИТНОСТЬ и РАЗРЕШЕНИЕ СРЕДНЕГО — разные величины: BITS задаёт + * мгновенный код на порту, а разрешение среднего растёт с длиной + * буфера m. + * + * ─── ПИНЫ (выравнивание по СТАРШЕМУ биту) ─── + * + * Старший бит значения всегда на GP15, данные занимают GP(16-BITS)..GP15: + * + * BITS=8 -> GP8 ... GP15 (вся гребёнка) + * BITS=4 -> GP12 ... GP15, GP8..GP11 не трогаются + * BITS=3 -> GP13 ... GP15 + * + * Выравнивание по старшему биту сохраняет полную шкалу при любом BITS. + * + * ─── СХЕМА СИНУСА (обязательна после ШИМ/PIO) ─── + * + * GP15 ──[R1 1к]──┬──[R2 1к]──┬──► OUT (вход DUT) + * │ │ + * [C1 47н] [C2 47н] + * │ │ + * GND GND + * + * При несущей ~62 кГц фильтр даёт f_c ~ 3,4 кГц и подавляет несущую + * примерно на 50 дБ. Постоянная составляющая = Uпит/2; где смещение + * недопустимо — разделительный конденсатор 1 мкФ + 47 кОм на землю. + * + * ─── КОМАНДЫ (совместимы с 01-...ino и CircuitPython-версией) ─── + * + * F <Гц> — частота (1..200000) + * SF <Гц> — частота синуса + * W Q | W S — режим: Q — прямоугольник (ШИМ), S — синус (PIO) + * D <0..255> — скважность прямоугольника + * M <0..127> — амплитуда синуса + * A — размах на ADC0 (GP26), мВ -> "A <мВ>" + * SWEEP f0 f1 n ms — логарифмический свип: "P <Гц> <мВ>", затем "DONE" + * STOP — прервать свип + * HELP | ? — подробная справка + * + * ДОПОЛНИТЕЛЬНО (настройки PIO): + * + * B <1..8> — число бит порта + * O <1..64> — точек на сэмпл (m = N*O) + * N <16..4096> — длина периода в «полезных» сэмплах + * CLC <1..8> — тактов PIO на сэмпл (калибровка частоты) + * CAL <Гц> — автокалибровка CPC по измеренной частоте + * I — текущие настройки и расчётные частоты + * + * ВНИМАНИЕ: ADC0 (GP26) — вход 0..3,3 В. Сигнал больше — только через + * делитель. Схему без питания к ADC не подключать. + */ + +#include +#include + +// Проверка ядра: PIO требует arduino-pico (earlephilhower) +#if !defined(ARDUINO_ARCH_RP2040) || defined(ARDUINO_ARCH_MBED) +#error "Этот скетч требует ядра earlephilhower (arduino-pico): в mbed-ядре PIO/DMA недоступны. См. шапку файла." +#endif + +#include "hardware/pio.h" +#include "hardware/dma.h" +#include "hardware/pwm.h" +#include "hardware/clocks.h" + +// ─────────────────────────── пины ─────────────────────────── +#define PIN_OUT_MSB 15 // старший бит (data); диапазон GP(16-BITS)..GP15 +#define PIN_TRIG 16 // синхроимпульс (вне диапазона PIO) +#define PIN_ADC 26 // ADC0 = A0 + +// ─────────────────────── параметры ────────────────────────── +#define CLK_SYS_HZ 125000000UL +#define PWM_STEPS 255 +#define AMP_MAX 127 +// Лимит буфера: длина m должна быть СТЕПЕНЬЮ ДВОЙКИ, чтобы работал +// кольцевой режим DMA (ring). Размер кольца ограничен 15 битами = +// 32768 байт = 8192 сэмпла по 4 байта. +#define MAX_M 8192 + +// ───────────────────────── состояние ──────────────────────── +static volatile uint32_t g_freq = 1000; +static volatile uint8_t g_duty = 128; +static volatile uint8_t g_amp = AMP_MAX; +static volatile bool g_sweep = false; +static volatile int g_bits = 8; +static volatile int g_oversample = 4; +static volatile int g_n = 256; +static volatile int g_cpc = 2; // тактов PIO на сэмпл + +// Буфер сэмплов и ресурсы PIO/DMA. +// ВАЖНО: ring DMA требует, чтобы адрес буфера был КРАТЕН его размеру +// (например буфер 4096 байт должен стоять на адресе, кратном 4096). +// Обычный malloc такого не гарантирует, поэтому выделяем с запасом и +// выравниваем указатель вручную (см. buildBuffer). +static uint32_t *g_buf = nullptr; // выровненный указатель (для DMA) +static void *g_buf_raw = nullptr; // исходный блок (для free) +static volatile size_t g_buf_len = 0; +static PIO g_pio; +static int g_sm = -1; +static int g_dma = -1; +static int g_prog_offset = -1; // куда загружена PIO-программа +static float g_freq_actual = 0.0f; + +// ══════════════════════════════════════════════════════════════ +// ФОРМА СИГНАЛА — МЕНЯТЬ ЗДЕСЬ +// ══════════════════════════════════════════════════════════════ +// t — время внутри периода, 0..1 с шагом 1/m. Возврат в [-1, 1]. +static float waveform(float t) { + // Чистый синус (по умолчанию): + return sinf(2.0f * (float)M_PI * t); + + // Студент заменяет строку выше на свою, например: + // return 0.3f*sinf(2*M_PI*t) + 0.5f*sinf(4*M_PI*t); + // return 4.0f*fabsf(t - 0.5f) - 1.0f; // треугольник + // return 2.0f*t - 1.0f; // пила + // return (t < 0.5f) ? 1.0f : -1.0f; // меандр +} + +// ─────────────── PIO-программа (две инструкции) ────────────── +// Собирается из кодировщиков pio_instructions.h на этапе выполнения — +// pioasm не нужен, инструкции тривиальны. +static uint16_t g_prog[2]; + +static void buildPioProgram(int bits) { + // pull block -> 0x80a0 + g_prog[0] = (uint16_t)pio_encode_pull(false, true); + // out pins, bits -> 0x6000 | bits + g_prog[1] = (uint16_t)pio_encode_out(pio_pins, (uint)bits); +} + +// ─────────────── расчёт буфера (дельта-сигма) ──────────────── +static void freeBuffer() { + if (g_buf_raw) { free(g_buf_raw); g_buf_raw = nullptr; } + g_buf = nullptr; + g_buf_len = 0; +} + +static bool buildBuffer() { + int bits = g_bits; + long m = (long)g_n * g_oversample; + + // Кольцевой DMA требует: длина m — степень двойки, адрес кратен m*4. + // Подгоняем m вверх до ближайшей степени двойки. + long mp = 8; + while (mp < m) mp <<= 1; + if (mp > MAX_M) return false; + m = mp; + + uint32_t full = (1u << bits) - 1; + float amp = (float)g_amp / (float)AMP_MAX; + + freeBuffer(); + size_t bytes = (size_t)m * sizeof(uint32_t); + + // Выделяем с запасом и выравниваем вручную: ring DMA «заворачивает» + // только младшие биты адреса, поэтому адрес обязан быть кратен + // размеру буфера — иначе кольцо сместится относительно данных. + g_buf_raw = malloc(bytes + bytes); + if (!g_buf_raw) return false; + uintptr_t raw = (uintptr_t)g_buf_raw; + uintptr_t aligned = (raw + bytes - 1) & ~(uintptr_t)(bytes - 1); + g_buf = (uint32_t *)aligned; + g_buf_len = (size_t)m; + + // дельта-сигма 1-го порядка; два прохода (второй — установившийся) + float v = 0.0f, acc = 0.0f, prev_u = 0.0f; + bool have_prev = false; + // Период сигнала должен занимать m точек (k/m от 0 до 1). + // m — подогнанная до степени двойки длина, поэтому фаза доходит до 1. + size_t idx = 0; + for (int period = 0; period < 2; ++period) { + for (long k = 0; k < m; ++k) { + float t = (float)k / (float)m; + float u = (waveform(t) + 1.0f) * 0.5f * (float)full * amp; + if (u < 0.0f) u = 0.0f; + if (u > (float)full) u = (float)full; + // ограничение наклона цели до 1 за шаг + if (have_prev) { + float d = u - prev_u; + if (d > 1.0f) u = prev_u + 1.0f; + else if (d < -1.0f) u = prev_u - 1.0f; + } + prev_u = u; have_prev = true; + if (period == 0) { + // первый проход: только устанавливаем аккумулятор + acc += u - v; + if (acc >= 1.0f) { v += 1.0f; acc -= 1.0f; } + else if (acc <= -1.0f) { v -= 1.0f; acc += 1.0f; } + if (v < 0.0f) v = 0.0f; + else if (v > (float)full) v = (float)full; + } else { + acc += u - v; + if (acc >= 1.0f) { v += 1.0f; acc -= 1.0f; } + else if (acc <= -1.0f) { v -= 1.0f; acc += 1.0f; } + if (v < 0.0f) v = 0.0f; + else if (v > (float)full) v = (float)full; + g_buf[idx++] = (uint32_t)v; + } + } + } + return true; +} + +// ─────────────── настройка PIO + DMA ─────────────── +static void stopPio() { + if (g_sm >= 0) { + pio_sm_set_enabled(g_pio, g_sm, false); + pio_sm_clear_fifos(g_pio, g_sm); + } + if (g_dma >= 0) { + dma_channel_abort(g_dma); + } +} + +static void releasePio() { + stopPio(); + if (g_prog_offset >= 0) { + // убрать программу из памяти PIO: она всего 32 инструкции, + // при частых перезапусках без этого память переполнится + pio_program_t prog = { g_prog, 2, -1, 0 +#if PICO_PIO_VERSION > 0 + , 0 +#endif + }; + pio_remove_program(g_pio, &prog, (uint)g_prog_offset); + g_prog_offset = -1; + } + if (g_sm >= 0) { pio_sm_unclaim(g_pio, g_sm); g_sm = -1; } + if (g_dma >= 0) { dma_channel_unclaim(g_dma); g_dma = -1; } +} + +static bool startPio() { + releasePio(); + if (!g_buf || g_buf_len == 0) return false; + + int bits = g_bits; + if (bits < 1 || bits > 8) return false; + + buildPioProgram(bits); + + // выбрать свободный блок PIO + g_pio = pio0; + g_sm = pio_claim_unused_sm(g_pio, false); + if (g_sm < 0) { g_pio = pio1; g_sm = pio_claim_unused_sm(g_pio, false); } + if (g_sm < 0) return false; + + pio_program_t prog = { g_prog, 2, -1, 0 +#if PICO_PIO_VERSION > 0 + , 0 +#endif + }; + int offset = pio_add_program(g_pio, &prog); + if (offset < 0) { releasePio(); return false; } + g_prog_offset = offset; + + // пины: диапазон GP(16-bits)..GP15, выравнивание по старшему биту + int first = 16 - bits; + for (int p = first; p < 16; ++p) { + pio_gpio_init(g_pio, p); + } + pio_sm_set_consecutive_pindirs(g_pio, g_sm, first, bits, true); + + pio_sm_config c = pio_get_default_sm_config(); + sm_config_set_wrap(&c, offset, offset + 1); + sm_config_set_out_pins(&c, first, bits); + sm_config_set_out_shift(&c, true, false, 32); // right, без autopull + sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX); // 8 слов TX + pio_sm_init(g_pio, g_sm, offset, &c); + + // частота PIO: f_pio = f_out * CPC * m + float f_pio = (float)g_freq * (float)g_cpc * (float)g_buf_len; + if (f_pio < 1.0f) f_pio = 1.0f; + float max_f = (float)CLK_SYS_HZ; + if (f_pio > max_f) f_pio = max_f; + pio_sm_set_clkdiv(g_pio, g_sm, (float)CLK_SYS_HZ / f_pio); + g_freq_actual = f_pio / ((float)g_cpc * (float)g_buf_len); + + // DMA: циклический перенос буфера в TX FIFO state machine + g_dma = dma_claim_unused_channel(false); + if (g_dma < 0) { releasePio(); return false; } + + dma_channel_config dc = dma_channel_get_default_config(g_dma); + channel_config_set_transfer_data_size(&dc, DMA_SIZE_32); + channel_config_set_read_increment(&dc, true); + channel_config_set_write_increment(&dc, false); + channel_config_set_dreq(&dc, pio_get_dreq(g_pio, g_sm, true)); + + // КОЛЬЦЕВОЙ РЕЖИМ. Требование: длина буфера m — степень двойки, и + // адрес буфера выровнен на его размер. Тогда ring сам «заворачивает» + // адрес чтения, и один запуск DMA крутит буфер бесконечно: + // transfer_count = 0xFFFFFFFF (максимум), а адрес оборачивается + // каждые m слов. Без этого после одного прохода DMA остановится + // и PIO встанет на pull block — сигнал оборвётся. + size_t bytes = g_buf_len * sizeof(uint32_t); + uint ring_bits = 0; + while ((1u << ring_bits) < bytes) ++ring_bits; + channel_config_set_ring(&dc, false, ring_bits); + + dma_channel_configure(g_dma, &dc, + &g_pio->txf[g_sm], // куда: TX FIFO PIO + g_buf, // откуда: буфер + 0xFFFFFFFFu, // практически бесконечно + true); // запустить сразу + + pio_sm_set_enabled(g_pio, g_sm, true); + return true; +} + +// ─────────────── прямоугольник (аппаратный ШИМ) ─────────────── +static void squareOn(uint32_t hz, uint8_t duty) { + if (hz < 1) hz = 1; + gpio_set_function(PIN_OUT_MSB, GPIO_FUNC_PWM); + uint slice = pwm_gpio_to_slice_num(PIN_OUT_MSB); + uint32_t top = CLK_SYS_HZ / (hz * (PWM_STEPS + 1)); + if (top == 0) top = 1; + if (top > 65534) top = 65534; + // пересчёт фактической частоты + uint32_t hz_real = CLK_SYS_HZ / (top * (PWM_STEPS + 1)); + pwm_set_clkdiv(slice, 1.0f); + pwm_set_wrap(slice, (uint16_t)top); + pwm_set_gpio_level(PIN_OUT_MSB, (uint32_t)duty * (top + 1) / 255); + pwm_set_enabled(slice, true); + g_freq_actual = (float)hz_real; +} + +static void squareOff() { + uint slice = pwm_gpio_to_slice_num(PIN_OUT_MSB); + pwm_set_enabled(slice, false); + gpio_set_function(PIN_OUT_MSB, GPIO_FUNC_SIO); +} + +// ─────────────── режимы ─────────────── +static bool g_sine_mode = false; + +static void applyMode(bool sine) { + if (sine) { + squareOff(); + startPio(); + } else { + stopPio(); + releasePio(); + squareOn(g_freq, g_duty); + } + g_sine_mode = sine; +} + +static void setFrequency(uint32_t hz) { + if (hz < 1) hz = 1; + if (hz > 200000) hz = 200000; + g_freq = hz; + if (g_sine_mode) startPio(); + else squareOn(g_freq, g_duty); +} + +// ─────────────── измерение амплитуды ─────────────── +static uint32_t measureAmplitudeMv(uint32_t window_ms = 50) { + uint32_t t0 = millis(); + uint16_t vmin = 4095, vmax = 0; + while ((millis() - t0) < window_ms) { + uint16_t v = (uint16_t)analogRead(PIN_ADC); + if (v < vmin) vmin = v; + if (v > vmax) vmax = v; + } + return (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.0f) f0 = 1.0f; + if (f1 <= f0) f1 = f0 * 10.0f; + g_sweep = true; + 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)f); + triggerPulse(); + delay(dwell_ms); + uint32_t mv = measureAmplitudeMv(); + Serial.print("P "); Serial.print((uint32_t)f); + Serial.print(' '); Serial.println(mv); + f *= ratio; + } + Serial.println("DONE"); + setFrequency((uint32_t)f0); +} + +// ─────────────── справка ─────────────── +static void printHelp() { + Serial.println(F("# ─────────── DDS-генератор на PIO (Arduino): команды ───────────")); + Serial.println(F("# РЕЖИМ И ЧАСТОТА")); + Serial.println(F("# W Q / W S — прямоугольник (ШИМ) / синус (PIO)")); + Serial.println(F("# F <Гц> — частота, 1..200000")); + Serial.println(F("# SF <Гц> — частота синуса")); + Serial.println(F("# ФОРМА")); + Serial.println(F("# D <0..255> — скважность (128 = 50 %), режим Q")); + Serial.println(F("# M <0..127> — амплитуда синуса")); + Serial.println(F("# ПРИБОР")); + Serial.println(F("# A — размах на ADC0 (GP26), мВ")); + Serial.println(F("# SWEEP f0 f1 n ms — логарифмический свип")); + Serial.println(F("# STOP — прервать свип")); + Serial.println(F("# НАСТРОЙКИ PIO")); + Serial.println(F("# B <1..8> — число бит порта (MSB на GP15)")); + Serial.println(F("# O <1..64> — точек на сэмпл (m = N*O)")); + Serial.println(F("# N <16..4096> — длина периода в сэмплах")); + Serial.println(F("# CLC <1..8> — тактов PIO на сэмпл (калибровка)")); + Serial.println(F("# CAL <Гц> — автокалибровка по измеренной частоте")); + Serial.println(F("# I — текущие настройки")); + Serial.println(F("# СПРАВКА: HELP или ?")); + Serial.println(F("# ФОРМА СИГНАЛА задаётся функцией waveform(t) в коде!")); + Serial.println(F("# ─────────────────────────────────────────────")); +} + +static void printInfo() { + // Фактическая длина буфера — g_buf_len (подогнана до степени двойки), + // а не N*O: кольцевой DMA требует степень двойки. + long m = (g_buf_len > 0) ? (long)g_buf_len : (long)g_n * g_oversample; + float f_pio = (float)g_freq * (float)g_cpc * (float)m; + float f_max = (float)CLK_SYS_HZ / ((float)g_cpc * (float)m); + float lv = (float)((1u << g_bits) - 1) * (float)m + 1.0f; + float ebits = logf(lv) / logf(2.0f); + Serial.print(F("# BITS=")); Serial.print(g_bits); + Serial.print(F(" OVERSAMPLE=")); Serial.print(g_oversample); + Serial.print(F(" (эфф.разрядность ")); Serial.print(ebits, 1); + Serial.print(F(" бит) N=")); Serial.print(g_n); + Serial.print(F(" BUFFER=")); Serial.print(m); + Serial.print(F(" (степень 2)")); + Serial.print(F(" AMP=")); Serial.print(g_amp); + Serial.print(F(" f_out=")); Serial.print(g_freq); + Serial.print(F(" (факт ")); Serial.print(g_freq_actual, 0); + Serial.print(F(") CPC=")); Serial.print(g_cpc); + Serial.print(F(" f_max=")); Serial.print(f_max, 0); + Serial.print(F(" Гц режим=")); Serial.print(g_sine_mode ? "S" : "Q"); + if (g_sine_mode && f_pio > (float)CLK_SYS_HZ) { + Serial.print(F(" ! ЗАПРОС ВЫШЕ ПОТОЛКА")); + } + Serial.println(); +} + +// ─────────────── разбор команд ─────────────── +static void handleLine(char *line) { + char *cmd = strtok(line, " \t"); + if (!cmd) return; + + if (!strcasecmp(cmd, "HELP") || !strcmp(cmd, "?")) { + printHelp(); + } else if (!strcasecmp(cmd, "I")) { + printInfo(); + } else 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, "SF")) { + char *a = strtok(nullptr, " \t"); + if (a) setFrequency((uint32_t)strtoul(a, nullptr, 10)); + Serial.print("OK SF "); Serial.println(g_freq); + } else if (!strcasecmp(cmd, "W")) { + char *a = strtok(nullptr, " \t"); + if (a) applyMode(!strcasecmp(a, "S")); + Serial.print("OK W "); Serial.println(g_sine_mode ? "S" : "Q"); + } else if (!strcasecmp(cmd, "D")) { + char *a = strtok(nullptr, " \t"); + if (a) { + g_duty = (uint8_t)constrain(atoi(a), 0, 255); + if (!g_sine_mode) squareOn(g_freq, g_duty); + } + 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); + buildBuffer(); + if (g_sine_mode) startPio(); + } + 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; + Serial.println(F("OK STOP")); + } else if (!strcasecmp(cmd, "B")) { + char *a = strtok(nullptr, " \t"); + if (a) { + g_bits = constrain(atoi(a), 1, 8); + if (buildBuffer() && g_sine_mode) startPio(); + } + Serial.print("OK B "); Serial.println(g_bits); + } else if (!strcasecmp(cmd, "O")) { + char *a = strtok(nullptr, " \t"); + if (a) { + g_oversample = constrain(atoi(a), 1, 64); + if (buildBuffer() && g_sine_mode) startPio(); + } + Serial.print("OK O "); Serial.println(g_oversample); + } else if (!strcasecmp(cmd, "N")) { + char *a = strtok(nullptr, " \t"); + if (a) { + g_n = constrain(atoi(a), 16, 4096); + if (buildBuffer() && g_sine_mode) startPio(); + } + Serial.print("OK N "); Serial.println(g_n); + } else if (!strcasecmp(cmd, "CLC")) { + char *a = strtok(nullptr, " \t"); + if (a) { + g_cpc = constrain(atoi(a), 1, 8); + if (g_sine_mode) startPio(); + } + Serial.print("OK CLC "); Serial.println(g_cpc); + } else if (!strcasecmp(cmd, "CAL")) { + char *a = strtok(nullptr, " \t"); + if (a) { + float meas = atof(a); + if (meas > 0.0f) { + float t = (float)g_freq * (float)g_cpc / meas; + g_cpc = constrain((int)lroundf(t), 1, 8); + if (g_sine_mode) startPio(); + } + } + Serial.print("OK CAL CPC="); Serial.println(g_cpc); + } else { + Serial.print(F("ERR unknown: ")); Serial.println(cmd); + } +} + +// ─────────────── setup / loop ─────────────── +void setup() { + Serial.begin(115200); + pinMode(PIN_TRIG, OUTPUT); + digitalWrite(PIN_TRIG, LOW); + analogReadResolution(12); + + if (!buildBuffer()) { + Serial.println(F("! ОШИБКА: не удалось построить буфер")); + } + squareOn(g_freq, g_duty); + + Serial.println(F("# DDS на PIO (Arduino/earlephilhower). Подробный хелп: HELP или ?")); + printHelp(); + printInfo(); +} + +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; + } + } +} diff --git a/task.md b/task.md index 9c86c8d..be8a651 100644 --- a/task.md +++ b/task.md @@ -113,11 +113,15 @@ K(f) = 1 / √(1 + (f/f_c)²), f_c = 1/(2π·R·C) | Файл | Платформа | Как формируется синус | |---|---|---| -| `01-...ip2040.ino` | Arduino C++ | программный DDS + ШИМ | +| `01-...ip2040.ino` | Arduino, **оба ядра** (earlephilhower и mbed) | программный DDS + ШИМ | | `01-...-circuitpython.py` | CircuitPython | программный DDS + ШИМ | -| `04-dds-pio-rp2040.py` | CircuitPython | **аппаратный PIO + DMA** | +| `04-dds-pio-rp2040.py` | CircuitPython | аппаратный PIO + DMA | +| `04-dds-pio-rp2040.ino` | Arduino, **только earlephilhower** | аппаратный PIO + DMA | -**Рекомендуется вариант на PIO** — он даёт точную частоту и не грузит CPU. +**Рекомендуется вариант на PIO** — точная частота, CPU свободен. +У `.ino`-варианта на PIO форма сигнала задаётся функцией `waveform(t)` +**в коде** (перекомпилировать после правки) — в C++ нет разбора формул +на лету, как в CircuitPython-версии. 1. Залейте файл на Pico (CircuitPython: переименовать в `code.py`). 2. Откройте консоль, введите `HELP` — увидите список команд.