From c194420e659f538a68a3f35b47ade38a0d03995f Mon Sep 17 00:00:00 2001 From: mss Date: Mon, 26 Oct 2026 15:33:38 +0400 Subject: [PATCH] =?UTF-8?q?ai:=20=D1=81=D0=BA=D0=B5=D1=82=D1=87=20DDS=20?= =?UTF-8?q?=D0=BD=D0=B5=20=D0=B7=D0=B0=D0=B2=D0=B8=D1=81=D0=B8=D1=82=20?= =?UTF-8?q?=D0=BE=D1=82=20analogWriteFreq=20=E2=80=94=20=D1=80=D0=B0=D0=B1?= =?UTF-8?q?=D0=BE=D1=82=D0=B0=D0=B5=D1=82=20=D0=BD=D0=B0=20=D0=BE=D0=B1?= =?UTF-8?q?=D0=BE=D0=B8=D1=85=20Arduino-=D1=8F=D0=B4=D1=80=D0=B0=D1=85=20R?= =?UTF-8?q?P2040=20(earlephilhower=20=D0=B8=20=D0=BE=D1=84=D0=B8=D1=86?= =?UTF-8?q?=D0=B8=D0=B0=D0=BB=D1=8C=D0=BD=D0=BE=D0=B5=20mbed)=20-=20=D0=BE?= =?UTF-8?q?=D1=84=D0=B8=D1=86=D0=B8=D0=B0=D0=BB=D1=8C=D0=BD=D0=BE=D0=B5=20?= =?UTF-8?q?=D1=8F=D0=B4=D1=80=D0=BE:=20=D1=81=D0=BE=D0=B1=D1=81=D1=82?= =?UTF-8?q?=D0=B2=D0=B5=D0=BD=D0=BD=D1=8B=D0=B9=20=D0=B4=D1=80=D0=B0=D0=B9?= =?UTF-8?q?=D0=B2=D0=B5=D1=80=20=D0=A8=D0=98=D0=9C=20=D0=BD=D0=B0=20=D1=80?= =?UTF-8?q?=D0=B5=D0=B3=D0=B8=D1=81=D1=82=D1=80=D0=B0=D1=85=20pico-sdk=20(?= =?UTF-8?q?hardware/pwm.h)=20-=20=D0=BF=D1=80=D1=8F=D0=BC=D0=BE=D1=83?= =?UTF-8?q?=D0=B3=D0=BE=D0=BB=D1=8C=D0=BD=D0=B8=D0=BA=20<=2020=20=D0=93?= =?UTF-8?q?=D1=86=20=E2=80=94=20=D0=BF=D1=80=D0=BE=D0=B3=D1=80=D0=B0=D0=BC?= =?UTF-8?q?=D0=BC=D0=BD=D1=8B=D0=B9=20=D0=B2=20ISR=20(=D0=B0=D0=BF=D0=BF?= =?UTF-8?q?=D0=B0=D1=80=D0=B0=D1=82=D0=BD=D1=8B=D0=B9=20=D0=A8=D0=98=D0=9C?= =?UTF-8?q?=20=D0=BD=D0=B5=20=D0=BD=D0=B8=D0=B6=D0=B5=20~7,5=20=D0=93?= =?UTF-8?q?=D1=86)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- stub/01-dds-generator-rp2040.ino | 135 ++++++++++++++++++++++++++++--- 1 file changed, 126 insertions(+), 9 deletions(-) diff --git a/stub/01-dds-generator-rp2040.ino b/stub/01-dds-generator-rp2040.ino index e0b5f73..e92f39c 100644 --- a/stub/01-dds-generator-rp2040.ino +++ b/stub/01-dds-generator-rp2040.ino @@ -1,12 +1,21 @@ /* * DDS-генератор сигналов для лабораторных работ - * Платформа: RP2040 (Raspberry Pi Pico, Arduino core — earlephilhower) + * Платформа: RP2040 (Raspberry Pi Pico) + * + * Работает в ОБОИХ Arduino-ядрах: + * - earlephilhower (arduino-pico) — штатный ШИМ analogWriteFreq/analogWriteRange; + * - официальное ядро "Raspberry Pi Pico/RP2040" (Arduino Mbed OS) — analogWriteFreq + * в нём отсутствует, поэтому включается собственный драйвер ШИМ на регистрах + * pico-sdk (hardware/pwm.h). Никакой настройки не требуется — ветка выбирается + * автоматически по макросу ARDUINO_ARCH_MBED. * * ЧЕРНОВИК (AI-сгенерировано, 2026-10-26). Требует проверки преподавателем * на реальном стенде: номиналы RC-фильтра, диапазоны частот, точность ADC. * * Возможности: - * - прямоугольник : аппаратный ШИМ, точная частота, независимая скважность; + * - прямоугольник : аппаратный ШИМ (точная частота, независимая скважность); + * ниже 20 Гц — «программный» прямоугольник в таймерном ISR + * (аппаратный ШИМ RP2040 не опускается ниже ~7,5 Гц); * - синус : таблица 256 точек + аккумулятор фазы (DDS) в таймерном * ISR -> ШИМ (несущая = SAMPLE_HZ = 62,5 кГц) -> внешний * ФНЧ (схема ниже); амплитуда — командой M; @@ -53,6 +62,18 @@ #include #include +#include "hardware/timer.h" // add_repeating_timer_us, repeating_timer_t — оба ядра + +// ───────────────── выбор драйвера ШИМ ───────────────── +// Официальное mbed-ядро не имеет analogWriteFreq/analogWriteRange — +// используем регистры pico-sdk. earlephilhower — штатный analogWrite*. +#if defined(ARDUINO_ARCH_MBED) + #define USE_CORE_PWM 0 + #include "hardware/pwm.h" + #include "hardware/gpio.h" +#else + #define USE_CORE_PWM 1 +#endif // ─────────────────────────── пины ─────────────────────────── #define PIN_OUT 15 // выход сигнала (прямоугольник — прямо; синус — через RC) @@ -70,6 +91,10 @@ #define PWM_RANGE 255 // 8-бит (для синуса); прямоугольнику хватает #define AMP_MAX 127 // максимальная амплитуда выборки (единиц duty) +// Прямоугольник ниже этой частоты генерится в ISR: аппаратный ШИМ RP2040 +// (div 8.4 + wrap 16 бит) физически не опускается ниже ~7,5 Гц. +#define SW_SQ_MIN_HZ 20UL + // ───────────────────────── состояние ──────────────────────── enum WaveMode : uint8_t { MODE_SQUARE = 0, MODE_SINE = 1 }; @@ -81,6 +106,85 @@ static volatile uint32_t g_phase = 0; // аккумулятор фазы (3 static volatile bool g_sweep = false; // флаг активного свипа static int8_t g_lut[256]; // sin: -127..127 +// ─────────────── универсальный слой ШИМ ─────────────── +// pwmStart() — включить ШИМ на PIN_OUT (idempotent) +// pwmConfig(f, range) — настроить частоту несущей +// pwmDuty(d) — установить скважность (0..255, масштабируется на текущий wrap) +// pwmStop() — отключить ШИМ, пин -> обычный GPIO (для программного ШИМ) + +#if USE_CORE_PWM +// earlephilhower arduino-pico +static void pwmStart() { /* analogWrite сам включает ШИМ */ } +static void pwmConfig(uint32_t freq_hz, uint32_t range) { + analogWriteFreq(freq_hz); + analogWriteRange(range); +} +static void pwmDuty(uint32_t duty) { + analogWrite(PIN_OUT, duty); +} +static void pwmStop() { + // digitalWrite сам переводит пин из ШИМ в GPIO + digitalWrite(PIN_OUT, LOW); +} + +#else +// официальное mbed-ядро: драйвер на регистрах pico-sdk +static uint slice_num, chan_num; +static uint32_t g_wrap = PWM_RANGE; + +// Точный подбор пары (wrap, clkdiv 8.4): фактическая частота несущей +// f = 125 МГц * 16 / ((wrap+1) * div8_4) +// Перебираем wrap в окне возле range (чтобы не терять разрядность ШИМ) +// и минимальной по модулю ошибке. +static void pwmConfig(uint32_t freq_hz, uint32_t range) { + const uint64_t CLK16 = 125000000ULL * 16; + uint32_t w_lo = 1, w_hi = 65535; + if (range > 1) { + w_lo = range - 1; + w_hi = (uint32_t)range + 1024; + if (w_hi > 65535) w_hi = 65535; + } + uint32_t best_wrap = g_wrap, best_div = 16; + int64_t best_err = INT64_MAX; + for (uint32_t wrap = w_lo; wrap <= w_hi; ++wrap) { + uint64_t den = (uint64_t)(wrap + 1) * freq_hz; + uint32_t d = (uint32_t)((CLK16 + den / 2) / den); + if (d < 16) d = 16; // div >= 1 + if (d > 4095) d = 4095; // div <= 255,9375 (поле 8.4) + int64_t fa = (int64_t)(CLK16 / ((uint64_t)(wrap + 1) * d)); + int64_t err = fa - (int64_t)freq_hz; + if (err < 0) err = -err; + if (err < best_err) { best_err = err; best_wrap = wrap; best_div = d; } + } + g_wrap = best_wrap; + pwm_set_clkdiv(slice_num, best_div / 16.0f); + pwm_set_wrap(slice_num, best_wrap); +} + +static void pwmDuty(uint32_t duty) { + // масштабируем duty (0..255) на текущий wrap + uint32_t level = (uint32_t)((uint64_t)duty * g_wrap / 255); + pwm_set_chan_level(slice_num, chan_num, level); +} + +static void pwmStart() { + slice_num = pwm_gpio_to_slice_num(PIN_OUT); + chan_num = pwm_gpio_to_channel(PIN_OUT); + gpio_set_function(PIN_OUT, GPIO_FUNC_PWM); + pwm_set_clkdiv(slice_num, 1.0f); + pwm_set_wrap(slice_num, PWM_RANGE); + pwm_set_chan_level(slice_num, chan_num, 0); + pwm_set_enabled(slice_num, true); +} + +static void pwmStop() { + pwm_set_enabled(slice_num, false); + gpio_set_function(PIN_OUT, GPIO_FUNC_SIO); + gpio_set_dir(PIN_OUT, GPIO_OUT); + gpio_put(PIN_OUT, LOW); +} +#endif // USE_CORE_PWM + // ─────────────────────── таблица синуса ───────────────────── static void buildLut() { for (int i = 0; i < 256; ++i) { @@ -98,8 +202,15 @@ static bool onSample(repeating_timer_t *) { uint8_t idx = (uint8_t)(g_phase >> 24); // центрируем на 128 и масштабируем амплитудой (g_amp 0..127) int32_t s = ((int32_t)g_lut[idx] * g_amp) / 127; - analogWrite(PIN_OUT, (uint8_t)(128 + s)); + pwmDuty((uint8_t)(128 + s)); + } else if (g_freq < SW_SQ_MIN_HZ) { + // «программный» прямоугольник: аппаратный ШИМ не тянет < 20 Гц + uint32_t step = + (uint32_t)((uint64_t)g_freq * (1ULL << 32) / SAMPLE_HZ); + g_phase += step; + digitalWrite(PIN_OUT, ((g_phase >> 24) < g_duty) ? HIGH : LOW); } + // в режиме SQUARE с f >= SW_SQ_MIN_HZ ШИМ работает аппаратно, ISR молчит return true; } @@ -107,16 +218,22 @@ static repeating_timer_t g_timer; // ─────────────────── применить режим/частоту ──────────────── static void applySquare() { - analogWriteFreq(g_freq); - analogWriteRange(PWM_RANGE); - // после analogWriteFreq/analogWriteRange текущее значение сбрасывается: - analogWrite(PIN_OUT, g_duty); + if (g_freq < SW_SQ_MIN_HZ) { + // программный прямоугольник (ISR), ШИМ выключен + pwmStop(); + digitalWrite(PIN_OUT, g_duty > 0 ? HIGH : LOW); + } else { + pwmStart(); + pwmConfig(g_freq, PWM_RANGE); + pwmDuty(g_duty); + } } static void applySine() { // частота ШИМ = SAMPLE_HZ (совпадает с ISR), размах — 8 бит - analogWriteFreq(CARRIER_HZ); - analogWriteRange(PWM_RANGE); + pwmStart(); + pwmConfig(SAMPLE_HZ, PWM_RANGE); + // уровень выборок обновляет ISR } static void setFrequency(uint32_t hz) {