Files
templates/c/set-protocol/docs/SIGNAL_GENERATOR.md

11 KiB
Raw Blame History

Генератор произвольного сигнала WG v1/v2

Расширение карты v2: F407 до 1 000 000 отсчётов/с

Версия карты в статусе — 2, set_wave_state.rate — uint32_t. Для записи частоты сначала остановить выход, записать старшие 16 бит в 0x1308, затем младшие 16 бит в 0x1303. Вторая запись проверяет диапазон 1…1 000 000 и применяет частоту; обе записи снимают ready. Чтение 0x1308 возвращает старшие биты применённой частоты, а слово 3 статуса — младшие. Host ABI: операция 9 — запись старшего слова, 8 — его чтение, 2 — запись младшего слова (включая ноль). Остальные команды не меняются.

Это версия карты регистров, а не транспорт: SETGUI использует SET v2 через EmulatorSerialPort, с резервным RTU для старой прошивки. Новый клиент понимает статус карт v1/v2; для v1 предел остаётся 50 000. Старый клиент отклоняет незнакомую версию 2 до записи. Код F407 и GUI поддерживают 1 МГц; порт G474 пока сохраняет прежний аппаратный лимит 50 кГц. Ниже описание исходной карты v1; пределы и регистры частоты заменены этим расширением для v2. На высоких частотах аналоговое установление зависит от величины скачка и нагрузки; аппаратная проверка осциллографом обязательна для оценки точности конкретного сигнала.

Общее C99-ядро строит кривые по точкам, преобразует напряжения в коды ЦАП и принимает таблицы через RTU поверх USB CDC/COM. Оно не зависит от Qt, HAL или ОС. Память предоставляет вызывающий; скрытого heap и глобального состояния нет.

SETGUI: точки -> Python/ctypes -> set_signal.c -> график / CSV / C
                               -> set_wavegen.c -> USB CDC
MCU: USB stream -> set_wave_rtu -> set_wave_port -> TIM6/DMA/DAC
Файл Зависимости и назначение
include/set_signal.h, src/set_signal.c C99/math: МНК 1…5, linear, PCHIP, natural cubic spline, 12-bit DAC
include/set_wavegen.h, src/set_wavegen.c C99 + set_crc.c: транзакционная загрузка и wire codec
ports/stm32f407-wavegen CMSIS F407: PA4/DAC1, TIM6, DMA1 Stream5 Channel7
ports/stm32g474-wavegen STM32G4 HAL: готовые DAC/TIM/DMA handles из CubeMX
python/set_devices/signal_reconstruction.py, waveform.py Модели, JSON/экспорт, ctypes; алгоритмов на Python нет
python/set_devices/wavegen_protocol.py, qt_ports/wavegen.py ctypes-кодек и Qt COM worker с проверкой чтением обратно

Интерполяция и таблица

set_signal_reconstruct принимает раздельные X/Y и workspace 14*N+128 элементов double. Сортировка, усреднение одинаковых времён, нормализация, МНК через QR и интерполяция выполняются в C. Максимум 100000 входных/10000 выходных точек. Переданный workspace может повторно использоваться. Большие расчёты предназначены для хоста; на MCU можно строить малые таблицы или принимать готовую таблицу без затрат на интерполяцию.

endpoint=1 включает последний X (анализ логов); endpoint=0 строит один период [0,T) без дублированного отсчёта стыка. Частота Fs и период T задают целое N=Fs*T; для WG максимум N=4096, Fs=1…50000 Гц, один канал, циклический выход. При разных значениях первой и последней точки на стыке будет скачок. Напряжения 0…Vref округляются к ближайшему коду 0…4095; выход за диапазон отклоняется целиком. Это относится и к выбросам сплайна. Vref должен соответствовать фактическому VDDA/VREF+ платы; это не программируемое питание.

Пример хоста (DLL передаётся через SETPROTOCOL_LIBRARY):

from set_devices.waveform import generate, c_header
points = [(0, 0), (10, 3.3), (20, 0)]
wave = generate(points, sample_rate=10000, vref=3.3, method="linear")
assert len(wave.codes) == 200
with open("waveform.h", "w", encoding="utf-8") as output:
    output.write(c_header(wave))

Контракт порта

start(context, samples, count, sample_rate) возвращает 0 при успехе; stop(context) синхронно прекращает DMA и устанавливает нулевой выход. Память таблицы принадлежит set_wave_state, её нельзя менять до stop. Вызывайте RTU и обслуживание состояния из одного основного потока, не из IRQ. IRQ USB помещает байты в очередь; USB-пакеты не являются границами RTU.

static uint16_t samples[SET_WAVE_MAX]; /* DMA-accessible SRAM, not CCM */
static set_wave_state wave;
static set_wave_f407 hardware = {72000000};
void application_init(void) {
    set_wave_port port = set_wave_f407_port(&hardware);
    set_wave_init(&wave, samples, SET_WAVE_MAX, &port);
}
size_t on_rtu(const uint8_t *frame, size_t n, uint8_t reply[256]) {
    return set_wave_rtu(&wave, 16, frame, n, reply, 256);
}

Для F407 DAC_CH1 — PA4, TIM6 TRGO_UPDATE, DMA1 Stream5/Channel7. Порт использует только нижние 16 бит DAC_CR и не меняет канал 2. Таблица лежит в SRAM1/2 0x20000000…0x2001FFFF, не в CCM. Перед первым отсчётом есть один такт предзагрузки последнего значения предыдущего периода. Для гарантированной целой частоты порт принимает только делители входной частоты TIM6; при 72 МГц подходят, например, 100, 1000, 5000, 10000, 20000 Гц. Частоту таймера передавайте с учётом удвоения при делителе APB1 > 1. Ошибку DMA/underrun проверяйте через set_wave_f407_fault() и останавливайте состояние. На F407-проекте этот вызов включён в главный цикл.

Для G474 настройте CubeMX: DAC1 channel1 (PA4), output buffer enabled, TIM6 TRGO_UPDATE, DMA memory-to-peripheral, circular, halfword/halfword, memory increment, запрос DMA_REQUEST_DAC1_CHANNEL1; подключите HAL IRQ для выбранного канала DMA и DAC. Передайте handles и timer_hz в set_wave_g474. Обработчики ошибок DMA/DAC в приложении должны вызвать stop и сбросить wave.running/ready. Порт проверен компиляцией с CubeG4 1.6.1; конкретная плата G474 здесь не прошита.

USB / Modbus RTU

Адрес по умолчанию 16, FC03/FC06, CRC16 Modbus. Максимальный запрос — 8 байт, ответ — 21 байт; существующий потоковый USB RTU parser F407 подходит без изменения формата. Все поля строит/проверяет C-кодек, GUI не пакует байты.

Регистр Значение
0x1300, FC03, 8 слов 0x5747, версия 1, running, Fs, count, received, ready, capacity
0x1302, FC06 0: stop и нулевой выход; 1: start только после commit
0x1303, FC06 Частота 1…50000; сбрасывает ready
0x1304, FC06 Начать загрузку, число отсчётов 2…4096; сброс received/ready
0xA000+i, FC06 Последовательная загрузка отсчёта 0…4095; повтор того же значения допустим
0xA000+i, FC03, 1 слово Чтение загруженного отсчёта для проверки
0x1306, FC06 0xA55A: commit, только если все отсчёты загружены

Во время running разрешены чтение и stop; запись/перезагрузка дают исключение 6. Неполная таблица не запускается. Повреждённый CRC и чужой адрес не меняют состояние. При reset ready/running=0, таблица не сохраняется во Flash. Профиль и регистры генератора не являются регистратором УМП или протоколом 2812.

Хост выполняет probe → stop → Fs → count → samples → readback каждого отсчёта → commit → status. Пуск — отдельная пользовательская команда. После потери USB уже запущенный DAC продолжает автономное воспроизведение; при таймауте состояние неизвестно до повторного status/stop. Одновременно используйте одного клиента, не открывайте этот же COM во вкладке подключения.

Потребители и проверки

  • SETGUI: «Логи и графики → Генератор», JSON, CSV и C header.
  • home/407vet6_emul_TMS_Periph: USB CDC и UART, штатный адрес настройки STM, отдельный PA4 DAC выход; HAL/CMSIS и настройки платы остаются в проекте.
  • G474: переносимый HAL-порт; board init остаётся в целевой прошивке.
  • tests/test_signal_wave.c: C-векторы интерполяции, DAC, CRC, неполной загрузки, readback и блокировки running. Общие байты проверены Python ctypes и фактическим USB stream parser эмулятора.

Аппаратная проверка амплитуды, периода и формы осциллографом не выполнена.