diff --git a/print/methodical-lab5-peredatochnaya-funkciya-na-ou-student.docx b/print/methodical-lab5-peredatochnaya-funkciya-na-ou-student.docx index 85ed022..4726d68 100644 Binary files a/print/methodical-lab5-peredatochnaya-funkciya-na-ou-student.docx and b/print/methodical-lab5-peredatochnaya-funkciya-na-ou-student.docx differ 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` — увидите список команд.