ai: скетч DDS не зависит от analogWriteFreq — работает на обоих Arduino-ядрах RP2040 (earlephilhower и официальное mbed)
- официальное ядро: собственный драйвер ШИМ на регистрах pico-sdk (hardware/pwm.h) - прямоугольник < 20 Гц — программный в ISR (аппаратный ШИМ не ниже ~7,5 Гц)
This commit is contained in:
@@ -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 <Arduino.h>
|
||||
#include <math.h>
|
||||
#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) {
|
||||
|
||||
Reference in New Issue
Block a user