ai: lab5 — скетч без зависимости от pico-sdk в mbed-ядре (mbed::PwmOut + mbed::Ticker) + альтернатива на CircuitPython (01-dds-generator-rp2040-circuitpython.py)

- mbed-ядро: только mbed API, hardware/pwm.h и hardware/timer.h не нужны
- CircuitPython: один цикл DDS + опрос USB-консоли (без threading), f_s меряется на старте
- прямоугольник < 20 Гц — программный (тот же ШИМ)
This commit is contained in:
mss
2026-10-26 15:43:35 +04:00
parent c194420e65
commit 7f8b52aa2c
4 changed files with 358 additions and 90 deletions
+60 -87
View File
@@ -3,11 +3,13 @@
* Платформа: 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.
* - 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.
*
* ЧЕРНОВИК (AI-сгенерировано, 2026-10-26). Требует проверки преподавателем
* на реальном стенде: номиналы RC-фильтра, диапазоны частот, точность ADC.
@@ -62,17 +64,17 @@
#include <Arduino.h>
#include <math.h>
#include "hardware/timer.h" // add_repeating_timer_us, repeating_timer_t — оба ядра
// ───────────────── выбор драйвера ШИМ ─────────────────
// Официальное mbed-ядро не имеет analogWriteFreq/analogWriteRange —
// используем регистры pico-sdk. earlephilhower — штатный analogWrite*.
// ───────────────── выбор бэкенда ─────────────────
// Официальное mbed-ядро: нет analogWriteFreq, нет pico-sdk в пользовательском
// коде -> только mbed API (PwmOut + Ticker). earlephilhower: analogWrite* +
// add_repeating_timer_us.
#if defined(ARDUINO_ARCH_MBED)
#define USE_CORE_PWM 0
#include "hardware/pwm.h"
#include "hardware/gpio.h"
#define USE_MBED_API 1
#include "mbed.h"
#else
#define USE_CORE_PWM 1
#define USE_MBED_API 0
#include "hardware/timer.h" // add_repeating_timer_us, repeating_timer_t
#endif
// ─────────────────────────── пины ───────────────────────────
@@ -110,9 +112,23 @@ 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
#if USE_MBED_API
// официальное mbed-ядро: PwmOut (16-бит размах внутри ядра)
static mbed::PwmOut g_pwm(digitalPinToPinName(PIN_OUT));
static void pwmStart() { /* PwmOut активен сразу */ }
static void pwmConfig(uint32_t freq_hz, uint32_t range) {
(void)range;
if (freq_hz < 1) freq_hz = 1;
g_pwm.period_us(1000000UL / freq_hz); // период в мкс (для 62,5 кГц = 16 мкс)
}
static void pwmDuty(uint32_t duty) {
if (duty > PWM_RANGE) duty = PWM_RANGE;
g_pwm.write((float)duty / (float)PWM_RANGE);
}
#else
// earlephilhower arduino-pico
static void pwmStart() { /* analogWrite сам включает ШИМ */ }
static void pwmConfig(uint32_t freq_hz, uint32_t range) {
@@ -122,68 +138,7 @@ static void pwmConfig(uint32_t freq_hz, uint32_t 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
#endif // USE_MBED_API
// ─────────────────────── таблица синуса ─────────────────────
static void buildLut() {
@@ -192,8 +147,12 @@ static void buildLut() {
}
}
// ───────────── ISR: обновление ШИМ для синуса (DDS) ─────────
static bool onSample(repeating_timer_t *) {
// ─────────────── тело ISR: обновление выборок ────────────────
// В режиме SINE — DDS-шаг фазы и новая скважность каждые 16 мкс;
// в режиме SQUARE с f < SW_SQ_MIN_HZ — программный прямоугольник
// (тот же ШИМ, только скважность 255 или 0 по фазе);
// в режиме SQUARE с f >= SW_SQ_MIN_HZ ШИМ работает аппаратно, ISR молчит.
static void sampleIsrBody() {
if (g_mode == MODE_SINE) {
// шаг фазы = f * 2^32 / Fs (64-бит, чтобы не переполнить)
uint32_t step =
@@ -204,24 +163,38 @@ static bool onSample(repeating_timer_t *) {
int32_t s = ((int32_t)g_lut[idx] * g_amp) / 127;
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);
pwmDuty(((g_phase >> 24) < g_duty) ? PWM_RANGE : 0);
}
// в режиме SQUARE с f >= SW_SQ_MIN_HZ ШИМ работает аппаратно, ISR молчит
return true;
}
#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;
}
static repeating_timer_t g_timer;
static void startSampleClock() {
add_repeating_timer_us(-(1000000L / SAMPLE_HZ), onSample, nullptr, &g_timer);
}
#endif
// ─────────────────── применить режим/частоту ────────────────
static void applySquare() {
if (g_freq < SW_SQ_MIN_HZ) {
// программный прямоугольник (ISR), ШИМ выключен
pwmStop();
digitalWrite(PIN_OUT, g_duty > 0 ? HIGH : LOW);
// программный прямоугольник (ISR): несущая = SAMPLE_HZ
pwmStart();
pwmConfig(SAMPLE_HZ, PWM_RANGE);
pwmDuty(g_duty > 0 ? PWM_RANGE : 0);
} else {
pwmStart();
pwmConfig(g_freq, PWM_RANGE);
@@ -338,7 +311,7 @@ void setup() {
pinMode(PIN_TRIG, OUTPUT);
digitalWrite(PIN_TRIG, LOW);
analogReadResolution(12); // ADC0 — 12 бит
add_repeating_timer_us(-(1000000L / SAMPLE_HZ), onSample, nullptr, &g_timer);
startSampleClock();
setMode(MODE_SQUARE);
setFrequency(1000);
Serial.println("# DDS generator ready (RP2040). help: F/W/D/A/SWEEP/STOP");