From 79411dbc26a9a9386f3e43e83514a74aadaa9bfd Mon Sep 17 00:00:00 2001 From: andrey Date: Mon, 28 Sep 2026 11:54:17 +0300 Subject: [PATCH] Add shared emulator services, waveform generation and STM32 USB boot ports --- c/set-protocol/CMakeLists.txt | 15 ++ c/set-protocol/README.md | 3 + c/set-protocol/docs/EMULATOR_SERVICES.md | 228 +++++++++++++++++ c/set-protocol/docs/SIGNAL_GENERATOR.md | 126 ++++++++++ c/set-protocol/include/set_boot.h | 26 ++ c/set-protocol/include/set_emu_igbt.h | 32 +++ c/set-protocol/include/set_emu_server.h | 48 ++++ c/set-protocol/include/set_emu_tms.h | 22 ++ c/set-protocol/include/set_emu_ump.h | 25 ++ c/set-protocol/include/set_regmap.h | 48 ++++ c/set-protocol/include/set_wavegen.h | 59 +++++ c/set-protocol/include/setprotocol.h | 2 + c/set-protocol/include/ump_protocol.h | 55 +++++ .../ports/example-emulator/service.c | 41 +++ .../stm32-usb-boot/boot_usb_protocol.inc | 54 ++++ .../ports/stm32f407-periph/README.md | 13 + .../ports/stm32f407-periph/ump_stm32f407.c | 30 +++ .../ports/stm32f407-periph/ump_stm32f407.h | 11 + .../ports/stm32f407-usb-boot/README.md | 68 +++++ .../stm32f407-usb-boot/boot_config.template.h | 9 + .../ports/stm32f407-usb-boot/boot_flash.c | 88 +++++++ .../ports/stm32f407-usb-boot/boot_image.h | 19 ++ .../ports/stm32f407-usb-boot/boot_main.c | 63 +++++ .../ports/stm32f407-usb-boot/boot_port.h | 13 + .../ports/stm32f407-usb-boot/boot_request.c | 26 ++ .../stm32f407-usb-boot/boot_usb_stream.c | 10 + .../stm32f407-usb-boot/test_boot_image.c | 34 +++ .../ports/stm32f407-wavegen/set_wave_f407.c | 53 ++++ .../ports/stm32f407-wavegen/set_wave_f407.h | 12 + .../ports/stm32g474-periph/README.md | 34 +++ c/set-protocol/ports/stm32g474-periph/board.c | 109 ++++++++ c/set-protocol/ports/stm32g474-periph/main.c | 39 +++ .../ports/stm32g474-periph/transport.c | 163 ++++++++++++ .../ports/stm32g474-periph/wave_hal.c | 116 +++++++++ .../ports/stm32g474-usb-boot/README.md | 47 ++++ .../stm32g474-usb-boot/boot_config.template.h | 5 + .../ports/stm32g474-usb-boot/boot_flash.c | 109 ++++++++ .../ports/stm32g474-usb-boot/boot_image.h | 34 +++ .../ports/stm32g474-usb-boot/boot_main.c | 39 +++ .../ports/stm32g474-usb-boot/boot_port.h | 15 ++ .../ports/stm32g474-usb-boot/boot_request.c | 26 ++ .../ports/stm32g474-usb-boot/boot_usb_port.c | 94 +++++++ .../stm32g474-usb-boot/boot_usb_stream.c | 10 + .../ports/stm32g474-usb-boot/boot_writer.h | 37 +++ .../ports/stm32g474-usb-boot/test_boot_port.c | 97 ++++++++ c/set-protocol/src/set_boot.c | 76 ++++++ c/set-protocol/src/set_emu_igbt.c | 142 +++++++++++ c/set-protocol/src/set_emu_server.c | 194 +++++++++++++++ c/set-protocol/src/set_emu_tms.c | 96 ++++++++ c/set-protocol/src/set_emu_ump.c | 172 +++++++++++++ c/set-protocol/src/set_regmap.c | 117 +++++++++ c/set-protocol/src/set_wavegen.c | 214 ++++++++++++++++ c/set-protocol/src/ump_protocol.c | 105 ++++++++ c/set-protocol/tests/test_boot.c | 70 ++++++ c/set-protocol/tests/test_emulator_services.c | 233 ++++++++++++++++++ c/set-protocol/tools/build_host.py | 1 + 56 files changed, 3627 insertions(+) create mode 100644 c/set-protocol/docs/EMULATOR_SERVICES.md create mode 100644 c/set-protocol/docs/SIGNAL_GENERATOR.md create mode 100644 c/set-protocol/include/set_boot.h create mode 100644 c/set-protocol/include/set_emu_igbt.h create mode 100644 c/set-protocol/include/set_emu_server.h create mode 100644 c/set-protocol/include/set_emu_tms.h create mode 100644 c/set-protocol/include/set_emu_ump.h create mode 100644 c/set-protocol/include/set_regmap.h create mode 100644 c/set-protocol/include/set_wavegen.h create mode 100644 c/set-protocol/include/ump_protocol.h create mode 100644 c/set-protocol/ports/example-emulator/service.c create mode 100644 c/set-protocol/ports/stm32-usb-boot/boot_usb_protocol.inc create mode 100644 c/set-protocol/ports/stm32f407-periph/README.md create mode 100644 c/set-protocol/ports/stm32f407-periph/ump_stm32f407.c create mode 100644 c/set-protocol/ports/stm32f407-periph/ump_stm32f407.h create mode 100644 c/set-protocol/ports/stm32f407-usb-boot/README.md create mode 100644 c/set-protocol/ports/stm32f407-usb-boot/boot_config.template.h create mode 100644 c/set-protocol/ports/stm32f407-usb-boot/boot_flash.c create mode 100644 c/set-protocol/ports/stm32f407-usb-boot/boot_image.h create mode 100644 c/set-protocol/ports/stm32f407-usb-boot/boot_main.c create mode 100644 c/set-protocol/ports/stm32f407-usb-boot/boot_port.h create mode 100644 c/set-protocol/ports/stm32f407-usb-boot/boot_request.c create mode 100644 c/set-protocol/ports/stm32f407-usb-boot/boot_usb_stream.c create mode 100644 c/set-protocol/ports/stm32f407-usb-boot/test_boot_image.c create mode 100644 c/set-protocol/ports/stm32f407-wavegen/set_wave_f407.c create mode 100644 c/set-protocol/ports/stm32f407-wavegen/set_wave_f407.h create mode 100644 c/set-protocol/ports/stm32g474-periph/README.md create mode 100644 c/set-protocol/ports/stm32g474-periph/board.c create mode 100644 c/set-protocol/ports/stm32g474-periph/main.c create mode 100644 c/set-protocol/ports/stm32g474-periph/transport.c create mode 100644 c/set-protocol/ports/stm32g474-periph/wave_hal.c create mode 100644 c/set-protocol/ports/stm32g474-usb-boot/README.md create mode 100644 c/set-protocol/ports/stm32g474-usb-boot/boot_config.template.h create mode 100644 c/set-protocol/ports/stm32g474-usb-boot/boot_flash.c create mode 100644 c/set-protocol/ports/stm32g474-usb-boot/boot_image.h create mode 100644 c/set-protocol/ports/stm32g474-usb-boot/boot_main.c create mode 100644 c/set-protocol/ports/stm32g474-usb-boot/boot_port.h create mode 100644 c/set-protocol/ports/stm32g474-usb-boot/boot_request.c create mode 100644 c/set-protocol/ports/stm32g474-usb-boot/boot_usb_port.c create mode 100644 c/set-protocol/ports/stm32g474-usb-boot/boot_usb_stream.c create mode 100644 c/set-protocol/ports/stm32g474-usb-boot/boot_writer.h create mode 100644 c/set-protocol/ports/stm32g474-usb-boot/test_boot_port.c create mode 100644 c/set-protocol/src/set_boot.c create mode 100644 c/set-protocol/src/set_emu_igbt.c create mode 100644 c/set-protocol/src/set_emu_server.c create mode 100644 c/set-protocol/src/set_emu_tms.c create mode 100644 c/set-protocol/src/set_emu_ump.c create mode 100644 c/set-protocol/src/set_regmap.c create mode 100644 c/set-protocol/src/set_wavegen.c create mode 100644 c/set-protocol/src/ump_protocol.c create mode 100644 c/set-protocol/tests/test_boot.c create mode 100644 c/set-protocol/tests/test_emulator_services.c diff --git a/c/set-protocol/CMakeLists.txt b/c/set-protocol/CMakeLists.txt index 5165838..02667b5 100644 --- a/c/set-protocol/CMakeLists.txt +++ b/c/set-protocol/CMakeLists.txt @@ -11,6 +11,7 @@ set(SETPROTOCOL_V2_SOURCES src/set_protocol.c src/set_can.c src/set_firmware.c + src/set_boot.c src/set_telemetry.c src/set_plot.c src/set_trends.c @@ -36,6 +37,12 @@ set(SETPROTOCOL_LEGACY_SOURCES ) set(SETPROTOCOL_SOURCES + src/set_regmap.c + src/set_emu_ump.c + src/set_emu_tms.c + src/set_emu_igbt.c + src/set_emu_server.c + src/set_wavegen.c ${SETPROTOCOL_V2_SOURCES} ${SETPROTOCOL_LEGACY_SOURCES} ) @@ -81,6 +88,14 @@ endif() option(SETP_BUILD_TESTS "Build host tests" ON) if(SETP_BUILD_TESTS) enable_testing() + add_executable(test_emulator_services tests/test_emulator_services.c ports/example-emulator/service.c) + target_link_libraries(test_emulator_services PRIVATE setprotocol_static) + add_test(NAME emulator_services COMMAND test_emulator_services) + if(MSVC) + target_compile_options(test_emulator_services PRIVATE /UNDEBUG) + else() + target_compile_options(test_emulator_services PRIVATE -UNDEBUG) + endif() add_executable(test_altera_logic tests/test_altera_logic.c) target_link_libraries(test_altera_logic PRIVATE setprotocol_static) add_test(NAME altera_logic COMMAND test_altera_logic) diff --git a/c/set-protocol/README.md b/c/set-protocol/README.md index 8842a43..72f6d94 100644 --- a/c/set-protocol/README.md +++ b/c/set-protocol/README.md @@ -1,5 +1,8 @@ # SETProtocol +Универсальные сервисы эмуляторов: [адресное пространство, API и порт новой платы](docs/EMULATOR_SERVICES.md). +`set_regmap` объединяет УМП, 2812, IGBT и DAC за общим API для RTU, CAN и других оболочек. + Единое переносимое протокольное ядро SET для `SETGUI`, Android GUI, устройств и сервисных утилит. В одном C99-проекте собраны: diff --git a/c/set-protocol/docs/EMULATOR_SERVICES.md b/c/set-protocol/docs/EMULATOR_SERVICES.md new file mode 100644 index 0000000..cbdec5d --- /dev/null +++ b/c/set-protocol/docs/EMULATOR_SERVICES.md @@ -0,0 +1,228 @@ +# Универсальное адресное пространство эмуляторов + +Общий код находится в `templates/c/set-protocol`. Прошивка платы задаёт память, +GPIO и аппаратные callbacks; протокол и модели не включают STM32/HAL/USB/ОС, +не выделяют heap и не читают системное время. Все вызовы одного экземпляра +сериализуются главным циклом или блокировкой вызывающего. + +## Слои и точка расширения + +- `set_regmap.h/.c`: таблица непересекающихся регионов и общий вызов + `set_regmap_request(map, function, address, value, out, capacity, &count)`. + Function 3 читает `value` слов, function 6 пишет одно слово. +- `set_emu_ump`, `set_emu_tms`, `set_emu_igbt`, `set_wavegen`: + независимые экземпляры моделей. Callbacks получают **относительный offset**, + а не внешний адрес. Один модуль можно разместить несколько раз по разным адресам. +- `set_emu_server`: готовая композиция для текущего стенда, режим УМП/2812, + подготовка настроек с применением через 500 тиков. +- `set_regmap_rtu` и `set_regmap_can`: готовые транспортные кодеки над одной картой. + `set_emu_server_rtu/can` дополнительно сохраняют legacy поведение стенда. + USB CDC, UART, TCP или иной поток только доставляет полный RTU-кадр. + Другой протокол разбирает собственную оболочку и вызывает `set_regmap_request`. + +Вызов с неизвестной функцией возвращает 1; неизвестный адрес, RO-запись, +чтение через границу региона — 2; неверное значение или размер буфера — 3; +ошибка аппаратного порта — 4; занятый модуль — 6. При ошибке `count=0`. +Длина успешного чтения равна запрошенной, запись возвращает адрес/значение. +Максимум — 124 слова. RTU-обёртка требует буфер 253 байта до выполнения команды; +CAN — 124 слова. Недостаточный буфер не может выполнить запись без ответа. +При ошибке содержимое выходного буфера не используется. + +`set_regmap_init` проверяет границы 16-битного пространства, пересечения, +права и callbacks. Таблица и состояния принадлежат вызывающему и должны жить +дольше карты. После регистрации таблица неизменна; для изменения карты +инициализируйте её заново в момент, когда нет обслуживаемых запросов. + +## Каталог и стандартная карта + +Каталог доступен на адресе 0 через любой транспорт. + +| Слово заголовка | Значение | +|---|---| +| 0 | 0x5345, сигнатура SE | +| 1 | версия каталога 1 | +| 2 | количество регионов | +| 3 | начало дескрипторов: 8 | +| 4 | длина дескриптора: 8 | +| 5 | максимум слов в одном чтении: 124 | +| 6, 7 | резерв: 0 | + +Дескриптор: `kind, version, instance, base, words, flags, 0, 0`. +Flags: bit0 чтение, bit1 запись. Права описывают возможности региона; +отдельные регистры внутри могут быть только для чтения. Пропуски внутри +региона возвращают ошибку, если модуль их не поддерживает. + +| Kind | Стандартный адрес | Размер | Модуль | +|---|---|---|---| +| 1 | 0x1000 | 0x17C | УМП: статус, окно, онлайн | +| 2 | 0x1200 | 36 | управление, конфигурация и indexed capability | +| 3 | 0x1300 | 8 | генератор DAC | +| 4 | 0x1400 | 112 | IGBT, версия 3 | +| 5 | 0x1500 | 80 | 2812 через регистры | +| 6 | 0x2000 | capacity × 40 | архив/банк УМП | +| 7 | 0xA000 | wave capacity | таблица DAC | + +Эта карта — готовая композиция, а не требование к новым платам. +`set_regmap` позволяет выбирать адреса, подмножество и несколько экземпляров +модуля; каталог сообщает фактическую карту. Настройка UART и выбор УМП/2812 +принадлежат только готовому `set_emu_server`. Максимальная ёмкость УМП +в этой карте — 819 записей на банк; текущая STM32 использует 800. + +В режиме 2812 прямое обращение к регистрам УМП возвращает busy, legacy +RTU/CAN к этим диапазонам игнорируются для совместимости. Остальные сервисы +и каталог доступны. Регистры управления 2812 принимают запись только в режиме 2812. +Чтение его состояния не запускает модель. + +## IGBT + +Предыдущий локальный диапазон IGBT 0x1300 конфликтовал с DAC. Его alias +сохранить нельзя: он обращается к генератору. Используйте каталог kind=4 или +новый стандартный адрес 0x1400. + +| Offset | Назначение | +|---|---| +| 0, 1 | 0x4947, версия 3 | +| 2 | enable 0/1; выключение сразу очищает состояние RX и ожидаемые ACK | +| 3 | чтение последнего TX; запись подготовленной TX-маски | +| 4 | RX-маска | +| 5 | резерв 0 | +| 6 | FAULT-маска 0…255 | +| 7 | задержка ACK 0…1000 мс | +| 8 | ширина ACK 1…255 мс | +| 9 | счётчик фронтов modulo 65536; запись 0 сбрасывает | +| 10 | число каналов 8 | +| 11 | один шаг модели = 1 мс | +| 12 | источник TX: 0 переданные GPIO, 1 подготовленная маска | +| 13 | подготовленная TX-маска, чтение/запись | +| 14 | запись маски запуска одиночного TX; чтение 0 | +| 15 | длина записи канала: 12 слов | + +Версия 3 сохраняет первые 14 регистров. Общие delay/width при записи +устанавливают значения для всех каналов; при чтении показывают последнее +общее задание, а не поканальные значения. Полное состояние: FC03 0x1400 / 112 слов. +Запуск TX требует enabled=1, source=1, разрешённого TX канала и низкого TX; +повторный запуск высокого TX отвергается кодом 6. Длительность отсчитывает +сама модель, не клиент COM. Ручная запись TX отменяет все текущие TX-импульсы. +Выключение эмуляции отменяет импульсы и сбрасывает их TX; статическое задание +TX сохраняется. PB остаются входами: тестовый TX — внутренняя подстановка, +физическая обратная связь RX выводится на PC0…PC7. + +Поканальные регистры: `0x1410 + 12 * channel`, channel=0…7. + +| Offset канала | Значение | +|---|---| +| 0 | flags: bit0 разрешение TX, bit1 разрешение RX, bit2 ACK active-low | +| 1 | RX mode: 0 ACK, 1 физический 0, 2 физическая 1, 3 пропуск ACK | +| 2, 3 | задержка ACK ON 0…1000 мс, ширина 1…1000 мс | +| 4, 5 | задержка ACK OFF 0…1000 мс, ширина 1…1000 мс | +| 6 | одиночный TX: 1…60000 мс | +| 7 | маска фронтов для ACK: bit0 ON, bit1 OFF | +| 8 | ширина FAULT 1…60000 мс; 0 — удержание до снятия FAULT | +| 9 | RO счётчик фронтов канала, modulo 65536 | +| 10, 11 | RO оставшаяся задержка / длительность ACK в мс | + +Изменение flags/mode отменяет ожидаемый и активный ACK канала. +Отключённый RX и выключенная модель всегда дают физический 0. +Инверсия применяется к автоматическому ответу и idle при пропуске ACK; +ручные уровни 0/1 не инвертируются. Сброс общего счётчика сбрасывает и канальные. +FAULT моделирует длинный/удерживаемый ответ после фронта; это не модель Vce, +Vge, DESAT, внутренней защиты или блокировки конкретного силового драйвера. + +`set_igbt_tick(state, pins)` получает маску входов от порта платы. +Фронт на тике T запускает ACK на T+delay. Повторный фронт до начала ACK +перезапускает задержку; это определённая модельная обработка слишком частых команд. +Каждый из восьми каналов независим. FAULT удерживает RX после его активации; +снятие FAULT освобождает только ранее аварийные каналы и сохраняет обычные ACK. +Чтобы TX через регистры не перезаписывался GPIO, выберите source=1. + +**Текущая IGBT-модель имеет шаг 1 мс.** Она проверяет логическую последовательность +Vin/Vstat, но не воспроизводит ACK 250/380 нс и ширины профилей 1SP/1SD в DSLogic. +Такую аппаратную точность нельзя получить переносом модели или вызовом из SysTick: +для неё требуется отдельный быстрый аппаратный порт и соответствующая модель времени. + +## 2812 + +Состояние едино для legacy CMD_STD и адресного API: + +| Offset | Назначение | +|---|---| +| 0, 1 | 0x544D, версия 1 | +| 2, 3, 4 | running, частота и задание (int16 ×100 Гц) | +| 8…11 | четыре подготовленных analog int16 | +| 12…17 | шесть подготовленных управляющих байт (значение 0…255) | +| 18 | записать 1: атомарно применить подготовленную команду | +| 32…45 | 28 байт дискретов; младший байт слова — первый байт | +| 46…79 | 34 аналоговых int16 | +| остальные | чтение 0 | + +Модель остаётся упрощённой BALZAM_7: Go, выбор remote, Setspeed, +разгон 10 Гц/с и синтетическая телеметрия. Legacy CMD_STD имеет отдельный +адрес 2812, запрос 18 и ответ 101 байт, с проверкой CRC до изменения состояния. +Буфер ответа менее 101 байта запрещает выполнение команды. + +## УМП и DAC + +УМП сохраняет прежние записи по 40 слов, банк/архив, фронтовой триггер, +постинтервал 1000 мс и indexed-чтение без изменения курсора. Два разных +буфера по `capacity*40` слов предоставляет плата. Размещение SRAM/CCM +не является частью библиотеки. + +DAC использует прежний `set_wave_port` с `start/stop`, транзакционную загрузку, +commit, чтение таблицы и запрет изменений при работе. Регистровые callbacks +и прежний `set_wave_rtu` вызывают одну реализацию. До повторного init вызывающий +останавливает аппаратный вывод; библиотека не угадывает текущее состояние DMA. + +## Обёртки и новая плата + +Пример: [ports/example-emulator/service.c](../ports/example-emulator/service.c). +Он регистрирует два экземпляра IGBT и показывает прямой, RTU и CAN вызов. +Порт новой платы должен: + +1. Создать состояния и таблицу регионов, вызвать init и проверить результат. +2. Предоставить буферы и аппаратный DAC-порт, если нужен генератор. +3. Передавать реальные тики и входы; читать результат модели и обновлять GPIO. +4. Доставлять запросы через выбранную оболочку в одну карту, сериализуя доступ. + +CAN-envelope совместим с UMP: BE address/value/0x1F00/token/function. +Идентификаторы CAN и маршрутизацию задаёт порт. Ответ делится по два слова, +нечётное чтение дополняется нулём до чётного числа; клиент отбрасывает последнее +слово по длине исходного запроса. Токен 1…255; один незавершённый ответ на соединение. +Проверенная копия слов хранится до отправки всех фрагментов. + +`set_emu_stream` — только сборщик известных запросов FC03/FC06 и CMD_STD, +по одному экземпляру на поток. Он принимает монотонные uint32 миллисекунды, +сохраняет кадр между USB-пакетами, сбрасывает незавершённый после паузы >1000 мс +и восстанавливается после плохого CRC. Это не универсальный Modbus-парсер +FC16/FC23: для другого формата нужна другая тонкая оболочка. + +## Сборка и проверки + +C99-ядро подключается через `setprotocol_static`/DLL, определения публичного +API экспортируются через PCAN_ABI_API. Включить `set_regmap.h` или +`set_emu_server.h`. Для иной архитектуры проверить наличие uint8/16/32_t; +существующий 16-битный C28x octet-порт UMP остаётся отдельным, этот новый +байтовый API пока не проверен TI-компилятором. + +`test_emulator_services.c` проверяет каталог, пересечения, границы и короткие +буферы, два экземпляра, переназначение адресов, RTU/CAN/stream, совместное +состояние 2812 и DAC, длинные задержки IGBT и освобождение FAULT. +Существующие тесты прошивки дополнительно проверяют архив УМП, USB-фрагментацию, +смену режима/адресов и совместимость с декодерами SETGUI. + +## Выбор единственного эмулятора + +В SETGUI: «Эмулятор → Настройки STM → Тип эмулятора», затем +«Применить в STM и проверить» и «Открыть управление эмулятором». +Конфигурация версии 3: режим 0 — УМП, 1 — 2812, 2 — IGBT, 3 — ЦАП. +Регистр 0x1219 подготавливает режим; 0x1218 = 0xA55A применяет его через +500 мс после подтверждения. Статус FC03 0x1210 / 10 слов содержит версию 3, +действующий режим в слове 8 и подготовленный в слове 9. + +При смене режима останавливаются TIM6/DMA/DAC, ЦАП устанавливается в ноль, +IGBT отключается и его выходы обнуляются, 2812 сбрасывается, УМП останавливается. +Новый режим не запускается автоматически. Для IGBT нужно явно включить +эмуляцию; для ЦАП заново загрузить таблицу и дать команду пуска. +Повторное применение того же режима не прерывает текущую работу. +Записи невыбранным IGBT/ЦАП/2812 отклоняются с кодом 6; чтение статуса +и явные команды остановки IGBT/ЦАП доступны. Настройки связи доступны всегда. +После сброса выбран УМП, IGBT выключен, ЦАП не запущен. diff --git a/c/set-protocol/docs/SIGNAL_GENERATOR.md b/c/set-protocol/docs/SIGNAL_GENERATOR.md new file mode 100644 index 0000000..7c94757 --- /dev/null +++ b/c/set-protocol/docs/SIGNAL_GENERATOR.md @@ -0,0 +1,126 @@ +# Генератор произвольного сигнала WG v1 + +Общее C99-ядро строит кривые по точкам, преобразует напряжения в коды ЦАП и +принимает таблицы через RTU поверх USB CDC/COM. Оно не зависит от Qt, HAL или ОС. +Память предоставляет вызывающий; скрытого heap и глобального состояния нет. + +```text +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`): + +```python +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. + +```c +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 эмулятора. + +Аппаратная проверка амплитуды, периода и формы осциллографом не выполнена. diff --git a/c/set-protocol/include/set_boot.h b/c/set-protocol/include/set_boot.h new file mode 100644 index 0000000..2b5ac4d --- /dev/null +++ b/c/set-protocol/include/set_boot.h @@ -0,0 +1,26 @@ +#ifndef SET_BOOT_H +#define SET_BOOT_H +#include "set_firmware.h" + +/* Transport-independent, single-slot updater. invalidate MUST become durable + * before erase; commit is called only after read-back verification. */ +typedef struct { + bool (*invalidate)(void *user); + bool (*erase)(void *user, uint32_t size); + bool (*write)(void *user, uint32_t offset, const uint8_t *data, uint16_t size); + bool (*verify)(void *user, uint32_t size, uint32_t crc); + bool (*commit)(void *user, uint32_t size, uint32_t crc); + void (*activate)(void *user); +} setp_boot_ops; +typedef struct { + setp_boot_ops ops; + void *user; + uint32_t base, capacity, crc; + uint16_t block_size; + setp_fw_status_t status; +} setp_boot; +void setp_boot_init(setp_boot *b, const setp_boot_ops *ops, void *user, + uint32_t base, uint32_t capacity); +/* Returns SET status; FW_STATUS body is encoded by the caller. */ +uint16_t setp_boot_command(setp_boot *b, uint16_t type, const uint8_t *p, uint16_t n); +#endif diff --git a/c/set-protocol/include/set_emu_igbt.h b/c/set-protocol/include/set_emu_igbt.h new file mode 100644 index 0000000..45e4b76 --- /dev/null +++ b/c/set-protocol/include/set_emu_igbt.h @@ -0,0 +1,32 @@ +#ifndef SET_EMU_IGBT_H +#define SET_EMU_IGBT_H +#include "set_regmap.h" +#ifdef __cplusplus +extern "C" { +#endif +#define SET_IGBT_CHANNELS 8u +#define SET_IGBT_WORDS 112u +#define SET_IGBT_CHANNEL_WORDS 12u +typedef struct { + uint16_t flags, mode, on_delay, on_width, off_delay, off_width; + uint16_t tx_width, edges, fault_width, count; +} set_igbt_channel; +typedef struct { + uint8_t enabled, tx, previous_tx, rx, fault, injected_tx, input_source; + uint16_t pending[SET_IGBT_CHANNELS], active[SET_IGBT_CHANNELS]; + uint16_t delay_ms, width_ms, event_count; + uint8_t logical_rx; + uint16_t pulse[SET_IGBT_CHANNELS], response_width[SET_IGBT_CHANNELS]; + set_igbt_channel channel[SET_IGBT_CHANNELS]; +} set_igbt_emu; +PCAN_ABI_API void set_igbt_init(set_igbt_emu *); +/* One model tick = 1 ms, no system clock. This coarse logical model does NOT + * reproduce the sub-microsecond timing of 1SP/1SD DSLogic hardware profiles. + * input_source=0 takes the supplied pins, 1 uses register-injected TX. */ +PCAN_ABI_API void set_igbt_tick(set_igbt_emu *, uint8_t pins); +PCAN_ABI_API unsigned set_igbt_read(void *, uint16_t offset, uint16_t count, uint16_t *); +PCAN_ABI_API unsigned set_igbt_write(void *, uint16_t offset, uint16_t value); +#ifdef __cplusplus +} +#endif +#endif diff --git a/c/set-protocol/include/set_emu_server.h b/c/set-protocol/include/set_emu_server.h new file mode 100644 index 0000000..6d18842 --- /dev/null +++ b/c/set-protocol/include/set_emu_server.h @@ -0,0 +1,48 @@ +/* Reference service composition for emulator boards; use set_regmap directly + * for a different map, subset, or multiple instances of any module. */ +#ifndef SET_EMU_SERVER_H +#define SET_EMU_SERVER_H +#include "set_emu_ump.h" +#include "set_emu_tms.h" +#include "set_emu_igbt.h" +#include "set_wavegen.h" +#ifdef __cplusplus +extern "C" { +#endif +#define SET_EMU_CONTROL_BASE 0x1200u +#define SET_EMU_IGBT_BASE 0x1400u +#define SET_EMU_TMS_BASE 0x1500u +#define SET_EMU_INDEXED_BASE 0x2000u +enum { SET_EMU_KIND_UMP=1,SET_EMU_KIND_CONTROL=2,SET_EMU_KIND_WAVE=3, + SET_EMU_KIND_IGBT=4,SET_EMU_KIND_TMS=5,SET_EMU_KIND_UMP_DATA=6, + SET_EMU_KIND_WAVE_DATA=7 }; +typedef struct { + set_regmap map; + set_reg_region regions[7]; + set_ump_emu ump; + set_tms_emu tms; + set_igbt_emu igbt; + set_wave_state *wave; + /* config[3]: 0=UMP, 1=TMS2812, 2=IGBT, 3=DAC. Configuration version 3. */ + uint16_t config[4],staged[4],apply_delay; +} set_emu_server; +/* bank_capacity in records, each bank has capacity*40 words. + * Wave may be NULL. UART rate index 0..4. No pins, heap or global instances. + * Init is for a quiescent instance: caller stops hardware before reinitializing. */ +PCAN_ABI_API unsigned set_emu_server_init(set_emu_server *,uint16_t *bank0, + uint16_t *bank1,uint16_t bank_capacity,set_wave_state *,unsigned address, + unsigned tms_address,unsigned baud_index,unsigned can_mode,unsigned enabled); +PCAN_ABI_API void set_emu_server_tick(set_emu_server *,uint8_t igbt_pins); +PCAN_ABI_API uint32_t set_emu_server_baud(const set_emu_server *); +PCAN_ABI_API size_t set_emu_server_rtu(set_emu_server *,const uint8_t *,size_t,uint8_t *,size_t); +PCAN_ABI_API size_t set_emu_server_can(set_emu_server *,const uint8_t *,size_t, + uint16_t *,size_t,unsigned *,unsigned *); +/* Stream adapter for FC03/06 (8 octets) and TMS CMD_STD (18 octets). One + * context per connection. Transport passes monotonic milliseconds. */ +typedef struct { uint8_t data[18],used;uint32_t last_byte; } set_emu_stream; +PCAN_ABI_API size_t set_emu_stream_feed(set_emu_stream *,set_emu_server *, + uint8_t byte,uint32_t now_ms,uint8_t *reply,size_t capacity); +#ifdef __cplusplus +} +#endif +#endif diff --git a/c/set-protocol/include/set_emu_tms.h b/c/set-protocol/include/set_emu_tms.h new file mode 100644 index 0000000..0f640b1 --- /dev/null +++ b/c/set-protocol/include/set_emu_tms.h @@ -0,0 +1,22 @@ +#ifndef SET_EMU_TMS_H +#define SET_EMU_TMS_H +#include "set_regmap.h" +#ifdef __cplusplus +extern "C" { +#endif +#define SET_TMS_WORDS 80u +typedef struct { + uint8_t controls[6], running; + uint16_t commands[10], divider; + int32_t frequency, target; +} set_tms_emu; +PCAN_ABI_API void set_tms_init(set_tms_emu *); +PCAN_ABI_API void set_tms_tick(set_tms_emu *); +PCAN_ABI_API unsigned set_tms_read(void *,uint16_t,uint16_t,uint16_t *); +PCAN_ABI_API unsigned set_tms_write(void *,uint16_t,uint16_t); +PCAN_ABI_API size_t set_tms_rtu(set_tms_emu *,unsigned device,const uint8_t *, + size_t,uint8_t *,size_t); +#ifdef __cplusplus +} +#endif +#endif diff --git a/c/set-protocol/include/set_emu_ump.h b/c/set-protocol/include/set_emu_ump.h new file mode 100644 index 0000000..492bd84 --- /dev/null +++ b/c/set-protocol/include/set_emu_ump.h @@ -0,0 +1,25 @@ +#ifndef SET_EMU_UMP_H +#define SET_EMU_UMP_H +#include "set_regmap.h" +#ifdef __cplusplus +extern "C" { +#endif +#define SET_UMP_WORDS 40u +typedef struct { + uint16_t (*banks[2])[SET_UMP_WORDS]; + uint16_t capacity,can_mode; + uint8_t enabled; + uint16_t state,used,next,bank,archive_bank,archive_count,archive_next,period,divider,cursor,source,generation,post,dropped,prev_command,prev_input,prev_output; + uint32_t elapsed,sequence,scenario; +} set_ump_emu; +/* Two distinct caller-owned arrays, capacity*40 words EACH. No heap/MCU layout. */ +PCAN_ABI_API unsigned set_ump_init(set_ump_emu *,uint16_t *,uint16_t *,uint16_t capacity, + uint16_t can_mode,uint8_t enabled); +PCAN_ABI_API void set_ump_tick(set_ump_emu *); /* one millisecond */ +PCAN_ABI_API unsigned set_ump_read(void *,uint16_t,uint16_t,uint16_t *); +PCAN_ABI_API unsigned set_ump_indexed_read(void *,uint16_t,uint16_t,uint16_t *); +PCAN_ABI_API unsigned set_ump_write(void *,uint16_t,uint16_t); +#ifdef __cplusplus +} +#endif +#endif diff --git a/c/set-protocol/include/set_regmap.h b/c/set-protocol/include/set_regmap.h new file mode 100644 index 0000000..a94677b --- /dev/null +++ b/c/set-protocol/include/set_regmap.h @@ -0,0 +1,48 @@ +/* Transport-independent 16-bit word address space. No heap, clocks or board headers. */ +#ifndef SET_REGMAP_H +#define SET_REGMAP_H +#include "pcan_abi.h" +#ifdef __cplusplus +extern "C" { +#endif +#define SET_REG_MAX_WORDS 124u +#define SET_REG_READ 1u +#define SET_REG_WRITE 2u +enum { SET_REG_OK=0, SET_REG_FUNCTION=1, SET_REG_ADDRESS=2, + SET_REG_VALUE=3, SET_REG_FAILURE=4, SET_REG_BUSY=6 }; +typedef unsigned (*set_reg_read_fn)(void *, uint16_t, uint16_t, uint16_t *); +typedef unsigned (*set_reg_write_fn)(void *, uint16_t, uint16_t); +typedef struct { + uint16_t base, words, kind, version, instance, flags; + void *context; + set_reg_read_fn read; + set_reg_write_fn write; +} set_reg_region; +typedef struct { + const set_reg_region *regions; + size_t count; +} set_regmap; +/* Directory at 0: 8-word header, then 8 words per region. Reject overlaps, + * wraparound, invalid callbacks and overlap with the directory itself. + * Regions and contexts must outlive the map and remain unchanged. */ +PCAN_ABI_API unsigned set_regmap_init(set_regmap *, const set_reg_region *, size_t); +/* One region per request. Preflight all bounds/capacity before calling a module. + * Writes return absolute address/value echo. Reads must be side-effect free. + * A module validates the entire write before mutation. Caller serializes access. */ +PCAN_ABI_API unsigned set_regmap_request(const set_regmap *, unsigned function, + uint16_t address, uint16_t value, uint16_t *out, size_t capacity, size_t *count); +/* Complete Modbus RTU FC03/06 frame; no broadcast. Capacity >=253 bytes. + * CRC/address/capacity validation precedes dispatch. */ +PCAN_ABI_API size_t set_regmap_rtu(const set_regmap *, unsigned device, + const uint8_t *, size_t, uint8_t *, size_t); +/* Legacy UMP CAN envelope, usable by any region: BE address/value/1F00/token/fn. + * Returns padded EVEN words, last padding word=0. Caller retains original + * requested count; token/error accompany fragments. Output capacity >=124. */ +PCAN_ABI_API size_t set_regmap_can(const set_regmap *, const uint8_t *, size_t, + uint16_t *, size_t, unsigned *token, unsigned *error); +PCAN_ABI_API size_t set_regmap_can_fragment(const uint16_t *, size_t count, + size_t index, unsigned token, unsigned error, uint8_t *, size_t capacity); +#ifdef __cplusplus +} +#endif +#endif diff --git a/c/set-protocol/include/set_wavegen.h b/c/set-protocol/include/set_wavegen.h new file mode 100644 index 0000000..06bbce7 --- /dev/null +++ b/c/set-protocol/include/set_wavegen.h @@ -0,0 +1,59 @@ +/** @file set_wavegen.h Transactional table upload and cyclic DAC playback. */ +#ifndef SET_WAVEGEN_H +#define SET_WAVEGEN_H +#include "pcan_abi.h" +#ifdef __cplusplus +extern "C" { +#endif +#define SET_WAVE_MAX 4096u +#define SET_WAVE_MAX_RATE 1000000u +#define SET_WAVE_BLOCK_MAX 120u +#define SET_WAVE_BASE 0x1300u +#define SET_WAVE_DATA 0xA000u +typedef struct { + void *context; + /* Return 0 on success. Samples remain owned by state until stop returns. + * start must arrange periodic output, typically DAC + timer + DMA. + * stop must quiesce DMA before returning and set output to zero. */ + int (*start)(void *, const uint16_t *, size_t, uint32_t); + void (*stop)(void *); +} set_wave_port; +typedef struct { + uint16_t *samples; + size_t capacity; + uint16_t count, received, ready, running, rate_high; + uint32_t rate; + set_wave_port port; +} set_wave_state; +void set_wave_init(set_wave_state *, uint16_t *, size_t, const set_wave_port *); +/* Atomic sample-block transfer. values=NULL builds a read; otherwise a write. + * At most 120 words: request <=249 bytes, response <=245 bytes. */ +PCAN_ABI_API size_t set_wave_block_request(unsigned device,unsigned index, + const uint16_t *values,size_t count,uint8_t *out,size_t capacity); +PCAN_ABI_API int set_wave_block_response(const uint8_t *request,size_t request_size, + const uint8_t *reply,size_t reply_size,uint16_t *words,size_t capacity); +size_t set_wave_block_rtu(set_wave_state *,unsigned device,const uint8_t *,size_t, + uint8_t *,size_t); +/* Relative register callbacks for set_regmap; status=8 words, samples=4096. + * These are the same state/validation used by the legacy RTU wrapper. */ +PCAN_ABI_API unsigned set_wave_read(void *,uint16_t,uint16_t,uint16_t *); +PCAN_ABI_API unsigned set_wave_write(void *,uint16_t,uint16_t); +PCAN_ABI_API unsigned set_wave_samples_read(void *,uint16_t,uint16_t,uint16_t *); +PCAN_ABI_API unsigned set_wave_samples_write(void *,uint16_t,uint16_t); +/* Caller serializes calls in the main loop. Returns complete reply size, + * 0 for unrelated/invalid frames. CRC verified before all state changes. */ +size_t set_wave_rtu(set_wave_state *, unsigned device, const uint8_t *, size_t, + uint8_t *, size_t); +/* Thin host transport ABI. Operations: 0=status, 1=stop, 2=sample rate, + * 3=begin upload(count), 4=sample(index,value), 5=commit, 6=start. + * 7=read a sample(index) for verification. Return 8 or 0. */ +PCAN_ABI_API size_t set_wave_request(unsigned device,unsigned operation,unsigned index, + unsigned value,uint8_t *out,size_t capacity); +/* Return 0=need more bytes, positive=word count, negative=invalid/exception. + * Validates exact echo/read shape, CRC, address and signature on status. */ +PCAN_ABI_API int set_wave_response(const uint8_t *request,const uint8_t *reply, + size_t size,uint16_t *words,size_t capacity); +#ifdef __cplusplus +} +#endif +#endif diff --git a/c/set-protocol/include/setprotocol.h b/c/set-protocol/include/setprotocol.h index 73b6929..3391a81 100644 --- a/c/set-protocol/include/setprotocol.h +++ b/c/set-protocol/include/setprotocol.h @@ -4,6 +4,8 @@ */ #ifndef SETPROTOCOL_H #define SETPROTOCOL_H +#include "set_regmap.h" +#include "set_emu_server.h" #include "balsam_can.h" #include "periph28335.h" diff --git a/c/set-protocol/include/ump_protocol.h b/c/set-protocol/include/ump_protocol.h new file mode 100644 index 0000000..2cee035 --- /dev/null +++ b/c/set-protocol/include/ump_protocol.h @@ -0,0 +1,55 @@ +/* UMP logger v2 (PM35), independent of PM67 and SET protocol v2. + * Octets use 16-bit storage: C28x has CHAR_BIT=16 and no uint8_t. + * All lengths below count wire octets or register words, never sizeof bytes. + */ +#ifndef UMP_PROTOCOL_H +#define UMP_PROTOCOL_H +#include +#include +#ifdef __cplusplus +extern "C" { +#endif +#define UMP_BASE 0x1000U +#define UMP_DATA 0x1010U +#define UMP_LIVE 0x1100U +#define UMP_MARKER 0x1F00U +#define UMP_MAX_WORDS 124U +#define UMP_RTU_CAPACITY 253U +typedef uint16_t ump_octet; +/* Called under caller's snapshot lock. write returns 1 on success, 0 otherwise. */ +typedef struct { + void *context; + uint16_t (*read)(void *context, uint16_t address); + int (*write)(void *context, uint16_t address, uint16_t value); +} ump_backend; +/* Returns Modbus exception 0/1/2/3. CAN has stricter whole-block reads. */ +unsigned ump_validate(unsigned function, uint16_t address, uint16_t value, int can); +/* reply capacity is >=124 words. Errors and writes return address/value echo. */ +unsigned ump_prepare(const ump_backend *backend, unsigned function, + uint16_t address, uint16_t value, int can, uint16_t *reply, unsigned *error); +uint16_t ump_crc(const ump_octet *data, size_t count); +/* Mode 1..16; kind 0=request, 1=normal telemetry, 2=logger reply; invalid ->0. */ +uint32_t ump_can_id(unsigned mode, unsigned kind); +/* Returns 0 for non-service frames, malformed size, or zero token. */ +unsigned ump_can_request(const ump_backend *backend, const ump_octet *request, + size_t size, uint16_t *reply, unsigned *token, unsigned *error); +/* count must be an even number 2..124; index counts pairs. Returns 8 or 0. */ +unsigned ump_can_fragment(const uint16_t *words, unsigned count, unsigned index, + unsigned token, unsigned error, ump_octet *reply); +/* Complete RTU frame only, after UART framing. Ignores bad CRC/foreign address. + * Caller provides >=253 octets; no writes occur on insufficient capacity. + * Device address 1..247; no broadcasts or legacy padding in this extension. + */ +size_t ump_rtu(const ump_backend *backend, unsigned device, + const ump_octet *request, size_t size, ump_octet *reply, size_t capacity); +/* Response for an already CRC-checked/framed request (legacy UART receiver). */ +size_t ump_rtu_response(const ump_backend *backend, unsigned device, unsigned function, + uint16_t address, uint16_t value, ump_octet *reply, size_t capacity); +/* Serialize a captured reply after releasing the snapshot lock. Returns 0 on + * invalid count/capacity; error replies need no words. */ +size_t ump_rtu_encode(unsigned device, unsigned function, unsigned error, + const uint16_t *words, unsigned count, ump_octet *reply, size_t capacity); +#ifdef __cplusplus +} +#endif +#endif diff --git a/c/set-protocol/ports/example-emulator/service.c b/c/set-protocol/ports/example-emulator/service.c new file mode 100644 index 0000000..15890cc --- /dev/null +++ b/c/set-protocol/ports/example-emulator/service.c @@ -0,0 +1,41 @@ +/* Minimal board-independent composition. This compiles without any MCU headers. + * Adapt these entry points to the selected board's network/USB/UART/CAN port. + * Run every call in one task (or protect the entire call with the same lock). */ +#include "set_emu_igbt.h" +static set_igbt_emu drivers[2]; +static set_reg_region regions[2]; +static set_regmap map; +unsigned example_service_init(void) +{ + unsigned i; + for(i=0;i<2;++i) { + set_reg_region *r=®ions[i]; + set_igbt_init(&drivers[i]); + r->base=(uint16_t)(0x300+i*0x100);r->words=SET_IGBT_WORDS; + r->kind=4;r->version=2;r->instance=(uint16_t)i;r->flags=3; + r->context=&drivers[i];r->read=set_igbt_read;r->write=set_igbt_write; + } + return set_regmap_init(&map,regions,2); +} +void example_service_tick(uint8_t inputs0,uint8_t inputs1,uint8_t outputs[2]) +{ + set_igbt_tick(&drivers[0],inputs0);set_igbt_tick(&drivers[1],inputs1); + outputs[0]=drivers[0].rx;outputs[1]=drivers[1].rx; +} +unsigned example_custom_protocol(unsigned function,uint16_t address,uint16_t value, + uint16_t *words,size_t capacity,size_t *count) +{ + /* Caller has already decoded/authenticated its own transport envelope. */ + return set_regmap_request(&map,function,address,value,words,capacity,count); +} +size_t example_uart_or_usb(const uint8_t *frame,size_t length,uint8_t *reply,size_t capacity) +{ + return set_regmap_rtu(&map,16,frame,length,reply,capacity); +} +size_t example_can(const uint8_t frame[8],uint16_t *snapshot,size_t capacity, + unsigned *token,unsigned *error) +{ + /* Select the CAN request ID outside the model; send the returned snapshot + * using set_regmap_can_fragment and the board's chosen response CAN ID. */ + return set_regmap_can(&map,frame,8,snapshot,capacity,token,error); +} diff --git a/c/set-protocol/ports/stm32-usb-boot/boot_usb_protocol.inc b/c/set-protocol/ports/stm32-usb-boot/boot_usb_protocol.inc new file mode 100644 index 0000000..7784fb0 --- /dev/null +++ b/c/set-protocol/ports/stm32-usb-boot/boot_usb_protocol.inc @@ -0,0 +1,54 @@ +/* Shared SET USB boot dispatcher. The MCU wrapper supplies BOOT_* macros. */ +#include "usb_setp_stream.h" +#include +static setp_boot boot; +static bool reset_pending; +static uint32_t reset_at; +typedef struct { uint8_t *out; uint16_t size; uint32_t now; } reply_context; +void boot_protocol_init(void) { setp_boot_init(&boot,&BOOT_OPS,0,BOOT_APP_BASE,BOOT_APP_SIZE); } +void boot_protocol_poll(uint32_t now) { + if(reset_pending && (int32_t)(now-reset_at)>=0) BOOT_RESET(); +} +static void dispatch(const setp_frame_t *r,void *user) { + reply_context *c=user; + uint8_t body[128]; uint16_t status=SETP_STATUS_OK; size_t n=0; + setp_frame_t response={0}; + if((r->destination!=0 && r->destination!=BOOT_NODE) || + (r->flags&(SETP_FLAG_RESPONSE|SETP_FLAG_EVENT))) return; + if(r->flags&(SETP_FLAG_ERROR|SETP_FLAG_MORE)) status=SETP_STATUS_INVALID_ARGUMENT; + else if(r->message_type>=SETP_MSG_FW_BEGIN && r->message_type<=SETP_MSG_FW_ACTIVATE) { + status=setp_boot_command(&boot,r->message_type,r->payload,r->payload_length); + if(!status && r->message_type==SETP_MSG_FW_STATUS) + n=setp_fw_status_encode(&boot.status,body+2,sizeof(body)-2); + if(!status && r->message_type==SETP_MSG_FW_ACTIVATE) { + reset_pending=true; reset_at=c->now+500; + } + } else if(r->payload_length) status=SETP_STATUS_INVALID_LENGTH; + else switch(r->message_type) { + case SETP_MSG_PING: setp_put_u32(body+2,c->now); n=4; break; + case SETP_MSG_DEVICE_INFO: { + static const uint8_t model[]=BOOT_MODEL; + const setp_device_info_t info={1,0,0,0x10000,3,0,sizeof(model)-1,model}; + n=setp_device_info_encode(&info,body+2,sizeof(body)-2); break; + } + case SETP_MSG_CAPABILITIES: { + const setp_capabilities_t caps={1,SETP_MAX_PAYLOAD, + SETP_IFACE_MASK(SETP_IFACE_USB_CDC),SETP_FEATURE_FIRMWARE,0,0,0,0}; + n=setp_capabilities_encode(&caps,body+2,sizeof(body)-2); break; + } + default: status=SETP_STATUS_UNSUPPORTED; break; + } + setp_put_u16(body,status); if(status) n=0; + response.flags=SETP_FLAG_RESPONSE|(status ? SETP_FLAG_ERROR : 0); + response.message_type=r->message_type; response.sequence=r->sequence; + response.source=BOOT_NODE; response.destination=r->source; + response.payload=body; response.payload_length=(uint16_t)(n+2); + c->size=(uint16_t)setp_frame_encode(&response,c->out,SETP_FRAME_MAX); +} +void usb_setp_reset(usb_setp_stream *s) { memset(s,0,sizeof(*s)); } +uint16_t usb_setp_feed(usb_setp_stream *s,uint8_t byte,uint32_t now,uint8_t *out) { + reply_context c={out,0,now}; + if(now-s->last_byte>1000) setp_parser_reset(&s->parser); + s->last_byte=now; (void)setp_parser_feed(&s->parser,&byte,1,dispatch,&c); + return c.size; +} diff --git a/c/set-protocol/ports/stm32f407-periph/README.md b/c/set-protocol/ports/stm32f407-periph/README.md new file mode 100644 index 0000000..73788e0 --- /dev/null +++ b/c/set-protocol/ports/stm32f407-periph/README.md @@ -0,0 +1,13 @@ +# Порт STM32F407 / эмулятор периферии + +Добавьте `src/ump_protocol.c` и `ump_stm32f407.c` в сборку. +Порт получает backend с read/write и переводит 8-битные буферы STM32 в +октеты ядра. Приём UART, CRC framing, CAN ID/DLC/RTR-фильтры и очередь +передачи находятся в проекте; ядро проверяет CRC полного RTU кадра. +Вызывать из главного цикла. В эмуляторе SysTick лишь накапливает тики, +поэтому read/write и генератор не выполняются одновременно. +Ответ RTU требует 253 байта; CAN snapshot — 124 uint16_t. Не передавайте +неполный snapshot в fragment, count должен быть фактическим числом слов. +Подключено в `home/407vet6_emul_TMS_Periph/Src/shared_ump.c`. + +[Контракт, пример и проверки](../../docs/UMP_PROTOCOL.md). diff --git a/c/set-protocol/ports/stm32f407-periph/ump_stm32f407.c b/c/set-protocol/ports/stm32f407-periph/ump_stm32f407.c new file mode 100644 index 0000000..5c97d87 --- /dev/null +++ b/c/set-protocol/ports/stm32f407-periph/ump_stm32f407.c @@ -0,0 +1,30 @@ +#include "ump_stm32f407.h" +size_t ump_stm32f407_rtu(const ump_backend *b, unsigned device, + const uint8_t *request, size_t length, uint8_t *reply, size_t capacity) +{ + ump_octet r[8],out[UMP_RTU_CAPACITY]; + size_t i,n; + if (!request || !reply || length!=8 || capacity +#define META 0x0800C000UL +#define COMMIT 0x53455442UL +#define ERRORS (FLASH_SR_PGSERR|FLASH_SR_PGPERR|FLASH_SR_PGAERR|FLASH_SR_WRPERR|FLASH_SR_SOP) +static void unlock(void) { + if(FLASH->CR & FLASH_CR_LOCK) { FLASH->KEYR=0x45670123; FLASH->KEYR=0xCDEF89AB; } + FLASH->SR=ERRORS; +} +static bool finish(void) { + while(FLASH->SR & FLASH_SR_BSY) {} + return !(FLASH->SR & ERRORS); +} +static void cache_reset(void) { + FLASH->ACR &= ~(FLASH_ACR_ICEN|FLASH_ACR_DCEN); + FLASH->ACR |= FLASH_ACR_ICRST|FLASH_ACR_DCRST; + FLASH->ACR &= ~(FLASH_ACR_ICRST|FLASH_ACR_DCRST); + FLASH->ACR |= FLASH_ACR_ICEN|FLASH_ACR_DCEN; + __DSB(); __ISB(); +} +static bool erase_sector(unsigned sector) { + bool ok; unlock(); + FLASH->CR=(FLASH->CR & ~(FLASH_CR_SNB|FLASH_CR_PSIZE)) | + FLASH_CR_SER | (2UL<CR|=FLASH_CR_STRT; ok=finish(); + FLASH->CR &= ~FLASH_CR_SER; FLASH->CR |= FLASH_CR_LOCK; + cache_reset(); return ok; +} +static bool program(uint32_t address, const uint8_t *p, uint32_t n) { + bool ok=true; unlock(); + /* Byte programming supports arbitrary SET block boundaries and final tails. */ + FLASH->CR=(FLASH->CR & ~FLASH_CR_PSIZE)|FLASH_CR_PG; + while(n--) { + *(volatile uint8_t *)address=*p; + if(!finish()) { ok=false; break; } + ++address; ++p; + } + FLASH->CR &= ~FLASH_CR_PG; FLASH->CR |= FLASH_CR_LOCK; + cache_reset(); return ok; +} +static bool invalidate(void *u) { (void)u; return erase_sector(3); } +static bool erase(void *u, uint32_t size) { + unsigned s; (void)u; + if(size<8 || size>F407_APP_SIZE) return false; + for(s=4;s<=7;++s) { + uint32_t start=s==4 ? F407_APP_BASE : 0x08020000UL+(s-5)*0x20000UL; + if(start>=F407_APP_BASE+size) break; + if(!erase_sector(s)) return false; + } + return true; +} +static bool write(void *u,uint32_t off,const uint8_t *p,uint16_t n) { + (void)u; + if(!n || off>F407_APP_SIZE || n>F407_APP_SIZE-off) return false; + return program(F407_APP_BASE+off,p,n) && !memcmp((const void *)(F407_APP_BASE+off),p,n); +} +static bool verify(void *u,uint32_t size,uint32_t crc) { + (void)u; + return f407_vectors_valid((const uint8_t *)F407_APP_BASE,size) && + setp_crc32((const uint8_t *)F407_APP_BASE,size)==crc; +} +static bool commit(void *u,uint32_t size,uint32_t crc) { + uint32_t record[4]={size,crc,~size,COMMIT}; (void)u; + return program(META,(const uint8_t *)record,sizeof(record)) && + !memcmp((const void *)META,record,sizeof(record)); +} +bool f407_app_valid(void) { + /* Like climate: direct Keil/SWD writes need no USB completion record. */ + return f407_vectors_valid((const uint8_t *)F407_APP_BASE,F407_APP_SIZE); +} +static void activate(void *u) { (void)u; /* main waits for USB response before reset */ } +const setp_boot_ops f407_boot_ops={invalidate,erase,write,verify,commit,activate}; +__attribute__((naked,noreturn)) static void enter(uint32_t sp,uint32_t pc) { + __asm volatile("msr msp, r0\nmovs r2, #0\nmsr control, r2\ndsb\nisb\ncpsie i\nbx r1\n"); +} +void f407_jump_app(void) { + unsigned i; uint32_t sp=*(const uint32_t *)F407_APP_BASE; + uint32_t pc=*(const uint32_t *)(F407_APP_BASE+4); + __disable_irq(); SysTick->CTRL=0; + for(i=0;i<8;++i) { NVIC->ICER[i]=0xffffffff; NVIC->ICPR[i]=0xffffffff; } + SCB->ICSR=SCB_ICSR_PENDSTCLR_Msk|SCB_ICSR_PENDSVCLR_Msk; + SCB->VTOR=F407_APP_BASE; enter(sp,pc); +} diff --git a/c/set-protocol/ports/stm32f407-usb-boot/boot_image.h b/c/set-protocol/ports/stm32f407-usb-boot/boot_image.h new file mode 100644 index 0000000..0988055 --- /dev/null +++ b/c/set-protocol/ports/stm32f407-usb-boot/boot_image.h @@ -0,0 +1,19 @@ +#ifndef F407_BOOT_IMAGE_H +#define F407_BOOT_IMAGE_H +#include "boot_port.h" +#include + +static inline bool f407_vectors_valid(const uint8_t *image, uint32_t size) +{ + uint32_t sp, pc; + if (size < 8 || size > F407_APP_SIZE) return false; + memcpy(&sp, image, 4); + memcpy(&pc, image + 4, 4); + return !(sp & 7) && + ((sp > 0x20000000UL && sp <= 0x20020000UL) || + (sp > 0x10000000UL && sp <= 0x10010000UL)) && + (pc & 1) && (pc & ~1UL) >= F407_APP_BASE && + (pc & ~1UL) < F407_APP_BASE + size; +} + +#endif diff --git a/c/set-protocol/ports/stm32f407-usb-boot/boot_main.c b/c/set-protocol/ports/stm32f407-usb-boot/boot_main.c new file mode 100644 index 0000000..ad6f4fd --- /dev/null +++ b/c/set-protocol/ports/stm32f407-usb-boot/boot_main.c @@ -0,0 +1,63 @@ +#include "stm32f4xx_hal.h" +#include "boot_config.h" +#include "usb_cdc.h" +#include "boot_port.h" +#if F407_BOOT_HSE_HZ != HSE_VALUE +#error boot_config.h and HAL HSE_VALUE must describe the same crystal +#endif +#if (F407_BOOT_HSE_HZ % 1000000UL) || F407_BOOT_HSE_HZ < 2000000UL || F407_BOOT_HSE_HZ > 50000000UL +#error HSE must provide a valid integer PLLM divider (2..50 MHz) +#endif +uint32_t SystemCoreClock=16000000; +const uint8_t AHBPrescTable[16]={0,0,0,0,0,0,0,0,1,2,3,4,6,7,8,9}; +const uint8_t APBPrescTable[8]={0,0,0,0,1,2,3,4}; +static uint8_t usb_clock_ready; +static volatile uint32_t clock_fault; +void SystemInit(void) { SCB->CPACR|=0xFu<<20; SCB->VTOR=0x08000000; } +void SysTick_Handler(void) { HAL_IncTick(); } +static void board_fatal(uint32_t code) { clock_fault=code; for(;;) {} } +static void peripheral_clock(void) +{ + RCC_OscInitTypeDef osc={0}; + RCC_ClkInitTypeDef clk={0}; + /* Разрешаем доступ к PWR и выбираем диапазон питания ядра перед повышением частоты. */ + __HAL_RCC_PWR_CLK_ENABLE(); + __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1); + osc.OscillatorType=RCC_OSCILLATORTYPE_HSE; + osc.HSEState=RCC_HSE_ON; + /* Готовность HSE проверяется HAL с ограниченным временем ожидания, а не бесконечным циклом. */ + if (HAL_RCC_OscConfig(&osc)==HAL_OK) usb_clock_ready=1; + else { + clock_fault=1; + osc.HSEState=RCC_HSE_OFF; + if (HAL_RCC_OscConfig(&osc)!=HAL_OK) board_fatal(3); + } + /* CPU 72 MHz, APB1 36 MHz, USB 48 MHz; USB requires the HSE crystal. */ + osc.OscillatorType=RCC_OSCILLATORTYPE_HSI; + osc.HSIState=RCC_HSI_ON; + osc.HSICalibrationValue=RCC_HSICALIBRATION_DEFAULT; + osc.PLL.PLLState=RCC_PLL_ON; + osc.PLL.PLLSource=usb_clock_ready ? RCC_PLLSOURCE_HSE : RCC_PLLSOURCE_HSI; + osc.PLL.PLLM=usb_clock_ready ? F407_BOOT_HSE_HZ/1000000UL : 16; + osc.PLL.PLLN=144; osc.PLL.PLLP=RCC_PLLP_DIV2; osc.PLL.PLLQ=3; + if (HAL_RCC_OscConfig(&osc)!=HAL_OK) board_fatal(3); + clk.ClockType=RCC_CLOCKTYPE_SYSCLK|RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2; + clk.SYSCLKSource=RCC_SYSCLKSOURCE_PLLCLK; + clk.AHBCLKDivider=RCC_SYSCLK_DIV1; + clk.APB1CLKDivider=RCC_HCLK_DIV2; clk.APB2CLKDivider=RCC_HCLK_DIV1; + /* HAL задаёт два такта ожидания Flash и перестраивает SysTick для новой частоты CPU. */ + if (HAL_RCC_ClockConfig(&clk,FLASH_LATENCY_2)!=HAL_OK) board_fatal(3); +} + + +void boot_protocol_init(void); +void boot_protocol_poll(uint32_t now); +int main(void) { + /* Jump from reset, before initializing USB, DMA, clocks or interrupts. */ + bool requested=f407_boot_requested(); + if(!requested && f407_app_valid()) f407_jump_app(); + if(HAL_Init()!=HAL_OK) board_fatal(3); + peripheral_clock(); boot_protocol_init(); + if(usb_clock_ready) usb_cdc_init(); + for(;;) { uint32_t now=HAL_GetTick(); usb_cdc_poll(now); boot_protocol_poll(now); } +} diff --git a/c/set-protocol/ports/stm32f407-usb-boot/boot_port.h b/c/set-protocol/ports/stm32f407-usb-boot/boot_port.h new file mode 100644 index 0000000..dc562ef --- /dev/null +++ b/c/set-protocol/ports/stm32f407-usb-boot/boot_port.h @@ -0,0 +1,13 @@ +#ifndef F407_BOOT_PORT_H +#define F407_BOOT_PORT_H +#include "set_boot.h" +#define F407_APP_BASE 0x08010000UL +#define F407_APP_SIZE 0x00070000UL +#define F407_BOOT_REQUEST 0x1003U +extern const setp_boot_ops f407_boot_ops; +bool f407_app_valid(void); +bool f407_boot_requested(void); +void f407_request_boot(uint32_t now); +void f407_reset_poll(uint32_t now); +void f407_jump_app(void); +#endif diff --git a/c/set-protocol/ports/stm32f407-usb-boot/boot_request.c b/c/set-protocol/ports/stm32f407-usb-boot/boot_request.c new file mode 100644 index 0000000..c0a2f46 --- /dev/null +++ b/c/set-protocol/ports/stm32f407-usb-boot/boot_request.c @@ -0,0 +1,26 @@ +#include "boot_port.h" +#include "stm32f4xx_hal.h" +#define REQUEST_MAGIC 0x42555342UL +static uint32_t reset_at; +static bool reset_pending; +static void backup_access(void) +{ + __HAL_RCC_PWR_CLK_ENABLE(); + PWR->CR |= PWR_CR_DBP; + __HAL_RCC_RTC_ENABLE(); +} +bool f407_boot_requested(void) +{ + bool requested; + backup_access(); requested=RTC->BKP0R==REQUEST_MAGIC; + RTC->BKP0R=0; return requested; +} +void f407_request_boot(uint32_t now) +{ + backup_access(); RTC->BKP0R=REQUEST_MAGIC; + reset_at=now+500; reset_pending=true; +} +void f407_reset_poll(uint32_t now) +{ + if(reset_pending && (int32_t)(now-reset_at)>=0) NVIC_SystemReset(); +} diff --git a/c/set-protocol/ports/stm32f407-usb-boot/boot_usb_stream.c b/c/set-protocol/ports/stm32f407-usb-boot/boot_usb_stream.c new file mode 100644 index 0000000..017296d --- /dev/null +++ b/c/set-protocol/ports/stm32f407-usb-boot/boot_usb_stream.c @@ -0,0 +1,10 @@ +#include "stm32f4xx_hal.h" +#include "boot_port.h" +#include "boot_config.h" +#define BOOT_OPS f407_boot_ops +#define BOOT_APP_BASE F407_APP_BASE +#define BOOT_APP_SIZE F407_APP_SIZE +#define BOOT_NODE F407_BOOT_NODE +#define BOOT_MODEL F407_BOOT_MODEL +#define BOOT_RESET() NVIC_SystemReset() +#include "../stm32-usb-boot/boot_usb_protocol.inc" diff --git a/c/set-protocol/ports/stm32f407-usb-boot/test_boot_image.c b/c/set-protocol/ports/stm32f407-usb-boot/test_boot_image.c new file mode 100644 index 0000000..1b39c25 --- /dev/null +++ b/c/set-protocol/ports/stm32f407-usb-boot/test_boot_image.c @@ -0,0 +1,34 @@ +#include "boot_image.h" +#include +#include + +int main(void) +{ + uint32_t words[32] = {0x20020000, F407_APP_BASE + 9}; + uint8_t *image = (uint8_t *)words; + /* A Keil image needs no metadata. Replacing its body must still boot. */ + assert(f407_vectors_valid(image, F407_APP_SIZE)); + words[10] ^= 1; + assert(f407_vectors_valid(image, F407_APP_SIZE)); + words[0] = words[1] = 0xffffffff; + assert(!f407_vectors_valid(image, F407_APP_SIZE)); + words[0] = 0x20020000; words[1] = 0x08000101; + assert(!f407_vectors_valid(image, F407_APP_SIZE)); + words[1] = F407_APP_BASE + 8; + assert(!f407_vectors_valid(image, F407_APP_SIZE)); + words[1] = F407_APP_BASE + 9; words[0] = 0x20020001; + assert(!f407_vectors_valid(image, F407_APP_SIZE)); + words[0] = 0x10010000; + assert(f407_vectors_valid(image, F407_APP_SIZE)); + assert(!f407_vectors_valid(image, 8)); + assert(!f407_vectors_valid(image, 7)); + assert(!f407_vectors_valid(image, F407_APP_SIZE + 1)); + words[1] = F407_APP_BASE + F407_APP_SIZE + 1; + assert(!f407_vectors_valid(image, F407_APP_SIZE)); + words[1] = F407_APP_BASE + 9; + /* Like climate, vectors alone cannot establish image completeness. */ + memset(image + 8, 0xff, sizeof(words) - 8); + assert(f407_vectors_valid(image, F407_APP_SIZE)); + puts("F407 boot: Keil image replacement and vector checks passed"); + return 0; +} diff --git a/c/set-protocol/ports/stm32f407-wavegen/set_wave_f407.c b/c/set-protocol/ports/stm32f407-wavegen/set_wave_f407.c new file mode 100644 index 0000000..0e354af --- /dev/null +++ b/c/set-protocol/ports/stm32f407-wavegen/set_wave_f407.c @@ -0,0 +1,53 @@ +#include "set_wave_f407.h" +#include "stm32f4xx.h" +static void stop(void *context) { + unsigned limit=100000; (void)context; + RCC->AHB1ENR|=RCC_AHB1ENR_GPIOAEN|RCC_AHB1ENR_DMA1EN; + RCC->APB1ENR|=RCC_APB1ENR_DACEN|RCC_APB1ENR_TIM6EN; + (void)RCC->APB1ENR; + TIM6->CR1=0; + DAC->CR&=~(DAC_CR_DMAEN1|DAC_CR_TEN1); + DMA1_Stream5->CR&=~DMA_SxCR_EN; + while((DMA1_Stream5->CR&DMA_SxCR_EN)&&--limit) {} + /* Leave other DAC channel untouched. Channel 1 outputs zero on stop. */ + DAC->DHR12R1=0; + DMA1->HIFCR=0xF40u; + DAC->SR=DAC_SR_DMAUDR1; +} +static int start(void *context,const uint16_t *samples,size_t count,uint32_t rate) { + set_wave_f407 *c=(set_wave_f407 *)context; + uint32_t ticks,psc,arr; uintptr_t address=(uintptr_t)samples; + if(!c||!rate||rate>SET_WAVE_MAX_RATE||count<2||count>SET_WAVE_MAX||c->timer_hz%rate + ||address<0x20000000u||address+count*2>0x20020000u) return 1; + ticks=c->timer_hz/rate; + for(psc=1;psc<=65536;++psc) if(ticks%psc==0&&ticks/psc<=65536) break; + if(psc>65536||ticksCR&DMA_SxCR_EN) return 1; + GPIOA->MODER|=3u<<8; + GPIOA->PUPDR&=~(3u<<8); + TIM6->PSC=psc-1;TIM6->ARR=arr-1;TIM6->CNT=0; + TIM6->CR2=0;TIM6->EGR=TIM_EGR_UG;TIM6->SR=0; + TIM6->CR2=TIM_CR2_MMS_1; /* update -> TRGO */ + DMA1_Stream5->PAR=(uint32_t)&DAC->DHR12R1; + DMA1_Stream5->M0AR=(uint32_t)samples;DMA1_Stream5->NDTR=(uint32_t)count; + DMA1_Stream5->FCR=0; + DMA1_Stream5->CR=(7u<<25)|DMA_SxCR_DIR_0|DMA_SxCR_MINC|DMA_SxCR_CIRC + |DMA_SxCR_PSIZE_0|DMA_SxCR_MSIZE_0|DMA_SxCR_PL_1; + /* Prime the previous period's last sample; DMA supplies the next value + * after each trigger. Cyclic order is last,0,1,... with one priming tick. */ + DAC->CR&=~0xFFFFu; + DAC->DHR12R1=samples[count-1]; + DAC->CR|=DAC_CR_EN1|DAC_CR_TEN1|DAC_CR_DMAEN1; /* TSEL1=0: TIM6 */ + DMA1_Stream5->CR|=DMA_SxCR_EN; + TIM6->CR1=TIM_CR1_CEN; + return 0; +} +set_wave_port set_wave_f407_port(set_wave_f407 *context) { + set_wave_port port={context,start,stop};return port; +} +int set_wave_f407_fault(void) { + return (DMA1->HISR&(DMA_HISR_TEIF5|DMA_HISR_DMEIF5|DMA_HISR_FEIF5)) + ||(DAC->SR&DAC_SR_DMAUDR1); +} diff --git a/c/set-protocol/ports/stm32f407-wavegen/set_wave_f407.h b/c/set-protocol/ports/stm32f407-wavegen/set_wave_f407.h new file mode 100644 index 0000000..b535770 --- /dev/null +++ b/c/set-protocol/ports/stm32f407-wavegen/set_wave_f407.h @@ -0,0 +1,12 @@ +#ifndef SET_WAVE_F407_H +#define SET_WAVE_F407_H +#include "../../include/set_wavegen.h" +/* Owns PA4, DAC channel 1, TIM6 and DMA1 stream 5/channel 7. + * timer_hz is TIM6 input clock (twice APB1 when APB1 prescaler !=1). + * Rate: 1..SET_WAVE_MAX_RATE (1 MS/s), exact divisors of timer_hz only. + * Context and sample buffer must outlive playback; buffer must be in SRAM. */ +typedef struct { uint32_t timer_hz; } set_wave_f407; +set_wave_port set_wave_f407_port(set_wave_f407 *context); +/* Nonzero on DMA error or DAC underrun; caller stops state on failure. */ +int set_wave_f407_fault(void); +#endif diff --git a/c/set-protocol/ports/stm32g474-periph/README.md b/c/set-protocol/ports/stm32g474-periph/README.md new file mode 100644 index 0000000..15e27f7 --- /dev/null +++ b/c/set-protocol/ports/stm32g474-periph/README.md @@ -0,0 +1,34 @@ +# STM32G474RB emulator peripheral port + +Board adapters for the common UMP/TMS/IGBT server and waveform generator. +All peripheral source files have detailed Russian comments matching the F407 port. +The shared algorithms, codecs and register map stay in `../../src`. + +- `board.c`: application VTOR 0x08010000; HSI16/PLL 160 MHz, APB1 80 MHz; + HSI48/CRS USB; board GPIO and 1 ms model ticks. +- `transport.c`: USART2 PA2/PA3 RTU, optional RS485 DE PB15; + FDCAN1 PB8/PB9, Classic CAN, extended IDs, bounded polling and TX timeout. +- `wave_hal.c`: PA4 DAC1_CH1, TIM6, DMA1_Channel1 with DMAMUX DAC1 request; + 2..4096 samples in SRAM1/2, exact timer division, maximum 1 MS/s. +- `main.c`: service ordering and deferred reboot into the G474 USB bootloader. + +IGBT input bits 0..7 use PB0/PB1/PB2/PB10/PB11/PB12/PB13/PB14; +output bits use PC0..PC7. SWD, SWO and USB pins are preserved. +Clock, addresses and RS485 mode come from the board-supplied `board_config.h`. +The default internal clock does not guarantee CAN or waveform accuracy over +all temperature conditions; use a verified 24 MHz HSE source when required. + +The consumer supplies `board.h`, `transport.h`, `wave_hal.h`, `emulator.h`, +G4 HAL configuration and the common model composition. The reference project +is `Nucleo_G474RB` in the emulator repository. Compile `EMU_CAPACITY=400`: +each 32000-byte archive bank fits the MCU's 32 KiB CCM/96 KiB SRAM layout. +DMA samples stay in SRAM1/2. No F407 peripheral implementation is compiled. + +Link at 0x08010000 with a 64 KiB Flash limit; install the separate G474RB +USB bootloader first. Require 128 KiB silicon, DBANK=0, BFB2=0 and no bank swap. +The firmware rejects mismatched geometry before peripheral initialization. +Reuse `../stm32g474-usb-boot/boot_usb_port.c` and `boot_request.c` with the +shared USB CDC transport. Device model: STM32G474RB-EMU-EXPERIMENT. + +Host tests validate the shared software; peripheral timing and electrical +behavior have not yet been validated on a physical board. diff --git a/c/set-protocol/ports/stm32g474-periph/board.c b/c/set-protocol/ports/stm32g474-periph/board.c new file mode 100644 index 0000000..417ef3d --- /dev/null +++ b/c/set-protocol/ports/stm32g474-periph/board.c @@ -0,0 +1,109 @@ +/** + * Аппаратный порт STM32G474RB: векторы приложения, тактирование и GPIO. + * Алгоритмы UMP/TMS/IGBT находятся в общем C-ядре templates; здесь только пины. + */ +#include "board.h" +#include "board_config.h" +#include "transport.h" +#include "emulator.h" +#include "igbt_emulator.h" +#include "stm32g4xx_hal.h" +volatile uint32_t clock_fault; +uint32_t SystemCoreClock=16000000u; +const uint8_t AHBPrescTable[16]={0,0,0,0,0,0,0,0,1,2,3,4,6,7,8,9}; +const uint8_t APBPrescTable[8]={0,0,0,0,1,2,3,4}; +static uint8_t usb_clock_ready; +/* Восемь входов IGBT: PB0,PB1,PB2,PB10..PB14. PB3/SWO и PB8/PB9 CAN свободны. */ +static const uint16_t input_pins[8]={GPIO_PIN_0,GPIO_PIN_1,GPIO_PIN_2, + GPIO_PIN_10,GPIO_PIN_11,GPIO_PIN_12,GPIO_PIN_13,GPIO_PIN_14}; +/* До C runtime разрешены только регистровые операции; глобальные данные ещё не готовы. */ +void SystemInit(void) { SCB->CPACR|=0xFu<<20; SCB->VTOR=0x08010000u; } +void SystemCoreClockUpdate(void) { + RCC_ClkInitTypeDef config; uint32_t latency; + HAL_RCC_GetClockConfig(&config,&latency); + SystemCoreClock=HAL_RCC_GetSysClockFreq() >> AHBPrescTable[config.AHBCLKDivider >> RCC_CFGR_HPRE_Pos]; +} +/* ISR только считает миллисекунды: протоколы и модели обслуживаются главным циклом. */ +void SysTick_Handler(void) { HAL_IncTick(); } +void board_fatal(uint32_t code) { clock_fault=code; for (;;) {} } +static void peripheral_clock(void) +{ + RCC_OscInitTypeDef osc={0}; RCC_ClkInitTypeDef clk={0}; + RCC_PeriphCLKInitTypeDef peripheral={0}; RCC_CRSInitTypeDef crs={0}; + __HAL_RCC_PWR_CLK_ENABLE(); + /* Для 160 МГц нужен Range1 Boost; Flash 4 wait states по таблице ST. */ + if (HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1_BOOST)!=HAL_OK) board_fatal(3); + osc.OscillatorType=RCC_OSCILLATORTYPE_HSI|RCC_OSCILLATORTYPE_HSI48; + osc.HSIState=RCC_HSI_ON; osc.HSICalibrationValue=RCC_HSICALIBRATION_DEFAULT; + osc.HSI48State=RCC_HSI48_ON; + osc.PLL.PLLState=RCC_PLL_ON; osc.PLL.PLLSource=RCC_PLLSOURCE_HSI; + /* HSI16 / 4 * 80 / 2 = 160 МГц; PLLQ также 160 МГц, FDCAN берёт PCLK1. */ + osc.PLL.PLLM=RCC_PLLM_DIV4; +#if EMU_USE_HSE +#if EMU_HSE_HZ != 24000000u +#error This board profile supports 24 MHz HSE or internal HSI16 +#endif + osc.OscillatorType|=RCC_OSCILLATORTYPE_HSE; + osc.HSEState=EMU_HSE_BYPASS ? RCC_HSE_BYPASS : RCC_HSE_ON; + osc.PLL.PLLSource=RCC_PLLSOURCE_HSE; osc.PLL.PLLM=RCC_PLLM_DIV6; +#endif + osc.PLL.PLLN=80; osc.PLL.PLLP=RCC_PLLP_DIV2; + osc.PLL.PLLQ=RCC_PLLQ_DIV2; osc.PLL.PLLR=RCC_PLLR_DIV2; + if (HAL_RCC_OscConfig(&osc)!=HAL_OK) board_fatal(3); + clk.ClockType=RCC_CLOCKTYPE_SYSCLK|RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2; + clk.SYSCLKSource=RCC_SYSCLKSOURCE_PLLCLK; clk.AHBCLKDivider=RCC_SYSCLK_DIV1; + clk.APB1CLKDivider=RCC_HCLK_DIV2; clk.APB2CLKDivider=RCC_HCLK_DIV1; + if (HAL_RCC_ClockConfig(&clk,FLASH_LATENCY_4)!=HAL_OK) board_fatal(3); + /* USB не зависит от PLL/HSE. USART2 и FDCAN получают 80 МГц PCLK1. */ + peripheral.PeriphClockSelection=RCC_PERIPHCLK_USB|RCC_PERIPHCLK_FDCAN|RCC_PERIPHCLK_USART2; + peripheral.UsbClockSelection=RCC_USBCLKSOURCE_HSI48; + peripheral.FdcanClockSelection=RCC_FDCANCLKSOURCE_PCLK1; + peripheral.Usart2ClockSelection=RCC_USART2CLKSOURCE_PCLK1; + if (HAL_RCCEx_PeriphCLKConfig(&peripheral)!=HAL_OK) board_fatal(3); + __HAL_RCC_CRS_CLK_ENABLE(); + crs.Prescaler=RCC_CRS_SYNC_DIV1; crs.Source=RCC_CRS_SYNC_SOURCE_USB; + crs.Polarity=RCC_CRS_SYNC_POLARITY_RISING; + crs.ReloadValue=__HAL_RCC_CRS_RELOADVALUE_CALCULATE(48000000u,1000u); + crs.ErrorLimitValue=RCC_CRS_ERRORLIMIT_DEFAULT; + crs.HSI48CalibrationValue=RCC_CRS_HSI48CALIBRATION_DEFAULT; + HAL_RCCEx_CRSConfig(&crs); usb_clock_ready=1; +} +void board_init(void) +{ + GPIO_InitTypeDef gpio={0}; + /* Этот образ связан с RB-загрузчиком: 128 КБ Flash, непрерывная карта DBANK=0. */ + __HAL_RCC_SYSCFG_CLK_ENABLE(); + if (*(const uint16_t *)FLASHSIZE_BASE!=128u || + (FLASH->OPTR&(FLASH_OPTR_DBANK|FLASH_OPTR_BFB2)) || + (SYSCFG->MEMRMP&SYSCFG_MEMRMP_FB_MODE)) board_fatal(5); + peripheral_clock(); + __HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_GPIOB_CLK_ENABLE(); __HAL_RCC_GPIOC_CLK_ENABLE(); + __HAL_RCC_USART2_CLK_ENABLE(); __HAL_RCC_FDCAN_CLK_ENABLE(); + /* ST-LINK VCP физически подключён к PA2/PA3; выбираем USART2 AF7, формат 8N1. */ + gpio.Pin=GPIO_PIN_2|GPIO_PIN_3; gpio.Mode=GPIO_MODE_AF_PP; + gpio.Speed=GPIO_SPEED_FREQ_VERY_HIGH; gpio.Pull=GPIO_PULLUP; + gpio.Alternate=GPIO_AF7_USART2; HAL_GPIO_Init(GPIOA,&gpio); + gpio.Pin=GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_10|GPIO_PIN_11|GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_14; + gpio.Mode=GPIO_MODE_INPUT; gpio.Pull=GPIO_PULLDOWN; HAL_GPIO_Init(GPIOB,&gpio); + /* PC0..PC7 — восемь ответов IGBT. Устанавливаем нули до включения выходов. */ + gpio.Pin=0xff; gpio.Mode=GPIO_MODE_OUTPUT_PP; gpio.Pull=GPIO_NOPULL; + HAL_GPIO_WritePin(GPIOC,0xff,GPIO_PIN_RESET); HAL_GPIO_Init(GPIOC,&gpio); +#if EMU_RS485_DE + gpio.Pin=GPIO_PIN_15; HAL_GPIO_WritePin(GPIOB,GPIO_PIN_15,GPIO_PIN_RESET); + HAL_GPIO_Init(GPIOB,&gpio); +#endif + /* FDCAN1 Classic CAN: PB8 RX, PB9 TX. PA11/PA12 остаются для USB FS. */ + gpio.Pin=GPIO_PIN_8|GPIO_PIN_9; gpio.Mode=GPIO_MODE_AF_PP; + gpio.Pull=GPIO_PULLUP; gpio.Alternate=GPIO_AF9_FDCAN1; HAL_GPIO_Init(GPIOB,&gpio); + transport_init(); +} +uint8_t board_usb_ready(void) { return usb_clock_ready; } +/* Один шаг 1 мс. Бит n соответствует элементу input_pins[n], выход — PCn. */ +void board_tick(void) +{ + uint8_t inputs=0,outputs; unsigned i; + for(i=0;i<8;++i) if(HAL_GPIO_ReadPin(GPIOB,input_pins[i])==GPIO_PIN_SET) inputs|=(uint8_t)(1u<PSR&FDCAN_PSR_BO) && (can.Instance->CCCR&FDCAN_CCCR_INIT)) { + CLEAR_BIT(can.Instance->CCCR,FDCAN_CCCR_INIT); + count=0; + } + if(pending) { + if(!HAL_FDCAN_IsTxBufferMessagePending(&can,mailbox)) { + if(pending==1 && !(can.Instance->TXBTO&mailbox)) {count=0;++can_timeouts;} + pending=0; + } else if(pending==1 && (uint32_t)(now-sent_at)>=250u) { + (void)HAL_FDCAN_AbortTxRequest(&can,mailbox); + count=0;pending=2;++can_timeouts; + } + } + if(__HAL_FDCAN_GET_FLAG(&can,FDCAN_FLAG_RX_FIFO0_MESSAGE_LOST)) { + __HAL_FDCAN_CLEAR_FLAG(&can,FDCAN_FLAG_RX_FIFO0_MESSAGE_LOST);++can_overflows; + } + if(HAL_FDCAN_GetRxFifoFillLevel(&can,FDCAN_RX_FIFO0) && + HAL_FDCAN_GetRxMessage(&can,FDCAN_RX_FIFO0,&rx,data)==HAL_OK) { + if(rx.DataLength==FDCAN_DLC_BYTES_8 && rx.IdType==FDCAN_EXTENDED_ID && + rx.RxFrameType==FDCAN_DATA_FRAME && rx.FDFormat==FDCAN_CLASSIC_CAN && + rx.Identifier==0xBA0000u+EMU_CAN_MODE-1 && !count && !pending) { + count=emu_can(data,words,&tag,&error);fragment=0; + } + } + if(count && !pending && HAL_FDCAN_GetTxFifoFreeLevel(&can)) { + emu_can_fragment(words,fragment,tag,error,data); + tx.Identifier=0xBA0020u+EMU_CAN_MODE-1;tx.IdType=FDCAN_EXTENDED_ID; + tx.TxFrameType=FDCAN_DATA_FRAME;tx.DataLength=FDCAN_DLC_BYTES_8; + tx.ErrorStateIndicator=FDCAN_ESI_ACTIVE;tx.BitRateSwitch=FDCAN_BRS_OFF; + tx.FDFormat=FDCAN_CLASSIC_CAN;tx.TxEventFifoControl=FDCAN_NO_TX_EVENTS; + if(HAL_FDCAN_AddMessageToTxFifoQ(&can,&tx,data)==HAL_OK) { + mailbox=HAL_FDCAN_GetLatestTxFifoQRequestBuffer(&can); + pending=1;sent_at=now; + if(++fragment>=count/2)count=0; + } else {count=0;++can_timeouts;} + } +} + +/* HAL обслуживает TXE/TC и вызывает callback завершения; прикладной протокол в ISR не выполняется. */ +void USART2_IRQHandler(void) { HAL_UART_IRQHandler(&uart); } +/* Вызывается HAL после TC: последний стоп-бит уже вышел на линию, можно отключить передатчик RS-485. + * Также используется локально для возврата в приём, если запуск передачи завершился ошибкой. */ +void HAL_UART_TxCpltCallback(UART_HandleTypeDef *h) +{ + if (h!=&uart) return; +#if EMU_RS485_DE + HAL_GPIO_WritePin(GPIOB,GPIO_PIN_15,GPIO_PIN_RESET); +#endif + transmitting=0; +} +/* Принимает доступный байт без ожидания, накапливает кадр и определяет конец по паузе RTU. + * rx/tx имеют по 256 байт; invalid запрещает выполнение всего повреждённого кадра. + * now и last_rx измеряются в миллисекундах; переданный HAL буфер tx живёт до завершения ISR. */ +void transport_uart_poll(uint32_t now) +{ + static uint8_t rx[256], tx[256], invalid; + static uint16_t received; + static uint32_t last_rx; + uint32_t baud=uart.Init.BaudRate; + /* Изменение скорости применяется после окончания ответа на старой скорости. + * Незавершённый входной кадр сбрасывается, чтобы не объединить байты разных скоростей. */ + if (!transmitting && baud!=emu_uart_baud()) { + uart.Init.BaudRate=emu_uart_baud(); + if (HAL_UART_Init(&uart)!=HAL_OK) board_fatal(4); + received=0; invalid=0; return; + } + if (__HAL_UART_GET_FLAG(&uart,UART_FLAG_ORE) || __HAL_UART_GET_FLAG(&uart,UART_FLAG_FE) || + __HAL_UART_GET_FLAG(&uart,UART_FLAG_NE) || __HAL_UART_GET_FLAG(&uart,UART_FLAG_PE)) { + /* На G4 ошибки сбрасываются записью ICR. RQR удаляет ошибочный байт; + * чтение SR/DR, применяемое на F407, здесь не используется. */ + __HAL_UART_CLEAR_FLAG(&uart,UART_CLEAR_PEF|UART_CLEAR_FEF|UART_CLEAR_NEF|UART_CLEAR_OREF); + __HAL_UART_SEND_REQ(&uart,UART_RXDATA_FLUSH_REQUEST); + invalid=1; ++uart_errors; last_rx=now; + } else if (__HAL_UART_GET_FLAG(&uart,UART_FLAG_RXNE)) { + uint8_t b; + /* RXNE уже установлен; timeout=0 не допускает ожидания следующего байта. + * Во время передачи входные байты вычитываются и отбрасываются, чтобы не принять собственное эхо. */ + if (HAL_UART_Receive(&uart,&b,1,0)==HAL_OK && !transmitting) { + if (received19200 baud RTU t3.5 is 1.75 ms; 3 ticks guarantees >=2 ms. */ + /* Для 8N1 ниже порога считаем 3,5 символа по 10 бит с округлением и запасом одного тика. + * Выше 19200 бод используем 3 тика: с учётом фазы SysTick фактическая пауза не меньше 2 мс. */ + if (received && !transmitting && (uint32_t)(now-last_rx)>= + (baud>19200u ? 3u : (35000u+baud-1)/baud+1u)) { + uint16_t length=invalid ? 0 : emu_rtu(rx,received,tx); + received=0; invalid=0; + if (length) { + transmitting=1; +#if EMU_RS485_DE + HAL_GPIO_WritePin(GPIOB,GPIO_PIN_15,GPIO_PIN_SET); +#endif + /* Передача асинхронная: poll возвращается сразу. tx не перезаписывается, пока transmitting != 0. */ + if (HAL_UART_Transmit_IT(&uart,tx,length)!=HAL_OK) { + ++uart_errors; HAL_UART_TxCpltCallback(&uart); + } + } + } +} diff --git a/c/set-protocol/ports/stm32g474-periph/wave_hal.c b/c/set-protocol/ports/stm32g474-periph/wave_hal.c new file mode 100644 index 0000000..f6eb670 --- /dev/null +++ b/c/set-protocol/ports/stm32g474-periph/wave_hal.c @@ -0,0 +1,116 @@ +/** + * Аппаратный исполнитель генератора: таблица uint16_t в SRAM -> DMA1 -> DAC1/PA4. + * TIM6 задаёт частоту выборок аппаратными событиями TRGO; CPU не отправляет каждый отсчёт. + * Модуль владеет только DAC channel 1, TIM6 и DMA1 channel 1 и DMAMUX request DAC1_CHANNEL1. + */ +/* HAL backend owned by this board; the shared protocol remains hardware independent. */ +#include "wave_hal.h" +#include "stm32g4xx_hal.h" + +static DAC_HandleTypeDef dac; +static DMA_HandleTypeDef dma; +static TIM_HandleTypeDef timer; +static uint8_t initialized; + +/* Callback остановки set_wave_port. context не используется: имеется один статический комплект HAL handles. + * Сначала останавливаем источник триггеров, затем DMA/DAC. После первого запуска держим PA4 на нулевом коде; + * до инициализации функция ничего не делает. Второй канал DAC не перенастраивается. */ +static void stop(void *context) +{ + (void)context; + if (!initialized) return; + (void)HAL_TIM_Base_Stop(&timer); + (void)HAL_DAC_Stop_DMA(&dac,DAC_CHANNEL_1); + /* Keep channel 1 driving zero after stop; channel 2 is untouched. */ + { + DAC_ChannelConfTypeDef config={0}; + /* G4 при AHB=160 МГц требует HFSEL; выход направляем на внешний PA4. */ + config.DAC_HighFrequency=DAC_HIGH_FREQUENCY_INTERFACE_MODE_AUTOMATIC; + config.DAC_ConnectOnChipPeripheral=DAC_CHIPCONNECT_EXTERNAL; + config.DAC_UserTrimming=DAC_TRIMMING_FACTORY; + config.DAC_Trigger=DAC_TRIGGER_NONE; config.DAC_OutputBuffer=DAC_OUTPUTBUFFER_ENABLE; + (void)HAL_DAC_ConfigChannel(&dac,&config,DAC_CHANNEL_1); + (void)HAL_DAC_SetValue(&dac,DAC_CHANNEL_1,DAC_ALIGN_12B_R,0); + (void)HAL_DAC_Start(&dac,DAC_CHANNEL_1); + } +} +/* Callback запуска: samples — таблица кодов DAC, count — число отсчётов, rate — выборок/с. + * Таблица должна оставаться действительной и неизменной до stop(). Возвращает 0 при успехе, 1 при ошибке. + * Проверки формы сигнала выполняет общий генератор; здесь проверяется аппаратная выполнимость. */ +static int start(void *context,const uint16_t *samples,size_t count,uint32_t rate) +{ + GPIO_InitTypeDef gpio={0}; + DAC_ChannelConfTypeDef config={0}; + /* G4 при AHB=160 МГц требует HFSEL; выход направляем на внешний PA4. */ + config.DAC_HighFrequency=DAC_HIGH_FREQUENCY_INTERFACE_MODE_AUTOMATIC; + config.DAC_ConnectOnChipPeripheral=DAC_CHIPCONNECT_EXTERNAL; + config.DAC_UserTrimming=DAC_TRIMMING_FACTORY; + TIM_MasterConfigTypeDef master={0}; + /* При делителе APB1=2 таймер получает удвоенную частоту PCLK1, то есть 160 МГц. + * Эта формула соответствует дереву тактирования из board.c. */ + uint32_t ticks,psc,clock=HAL_RCC_GetPCLK1Freq()*2u; + uintptr_t address=(uintptr_t)samples; + (void)context; + /* Разрешены 2..SET_WAVE_MAX отсчётов, частота до 1 МГц и только точное деление частоты таймера. + * Для DMA используем SRAM1/2: буфер должен лежать в 0x20000000..0x20017FFF; CCM оставляем архиву. */ + if (!rate || rate>SET_WAVE_MAX_RATE || count<2 || count>SET_WAVE_MAX || clock%rate || + address<0x20000000u || address+count*2>0x20018000u) return 1; + ticks=clock/rate; + /* Подбираем целые (PSC+1) и (ARR+1), оба не больше 65536. + * Частота выборок = timer_clock / ((PSC+1)*(ARR+1)); в регистры HAL передаёт значения минус один. */ + for (psc=1;psc<=65536;++psc) if (ticks%psc==0 && ticks/psc<=65536) break; + if (psc>65536 || ticksBKP0R`, call +`g474_request_boot(now)` for an update request and `g474_reset_poll(now)` from +the main loop. Reset consumes and clears the backup-register request. + +Host test `test_boot_port.c` covers the 64 KiB slot boundaries, geometry rejection, vector validation, +every block size 1..256, all final-word tails, sequential offsets and failures. +Compile it with this directory and `../../include` on the include path. +Hardware USB/Flash/power-loss tests remain required on the target board. diff --git a/c/set-protocol/ports/stm32g474-usb-boot/boot_config.template.h b/c/set-protocol/ports/stm32g474-usb-boot/boot_config.template.h new file mode 100644 index 0000000..fcc99c5 --- /dev/null +++ b/c/set-protocol/ports/stm32g474-usb-boot/boot_config.template.h @@ -0,0 +1,5 @@ +#ifndef G474_BOOT_CONFIG_H +#define G474_BOOT_CONFIG_H +#define G474_BOOT_NODE 16U +#define G474_BOOT_MODEL "STM32G474RB-USB-BOOT" +#endif diff --git a/c/set-protocol/ports/stm32g474-usb-boot/boot_flash.c b/c/set-protocol/ports/stm32g474-usb-boot/boot_flash.c new file mode 100644 index 0000000..598d11c --- /dev/null +++ b/c/set-protocol/ports/stm32g474-usb-boot/boot_flash.c @@ -0,0 +1,109 @@ +#include "boot_port.h" +#include "boot_image.h" +#include "boot_writer.h" +#include "stm32g4xx_hal.h" +#include +#define COMMIT 0x53455442UL +static g474_writer writer; +static bool geometry_valid(void) +{ + __HAL_RCC_SYSCFG_CLK_ENABLE(); + return g474_geometry_supported(*(const uint16_t *)FLASHSIZE_BASE, + (FLASH->OPTR & FLASH_OPTR_DBANK) != 0, + (FLASH->OPTR & FLASH_OPTR_BFB2) != 0 || + (SYSCFG->MEMRMP & SYSCFG_MEMRMP_FB_MODE) != 0); +} +static bool erase_page(uint32_t address) +{ + FLASH_EraseInitTypeDef config = {0}; + uint32_t bank, page, error; + if (!g474_flash_page(address, (FLASH->OPTR & FLASH_OPTR_DBANK) != 0, + &bank, &page)) return false; + config.TypeErase = FLASH_TYPEERASE_PAGES; + config.Banks = bank == 2U ? FLASH_BANK_2 : FLASH_BANK_1; + config.Page = page; config.NbPages = 1; + return HAL_FLASHEx_Erase(&config, &error) == HAL_OK; +} +static bool invalidate(void *user) +{ + bool ok; (void)user; + if (!geometry_valid() || HAL_FLASH_Unlock() != HAL_OK) return false; + __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_ALL_ERRORS); + ok = erase_page(G474_META_BASE); + (void)HAL_FLASH_Lock(); return ok; +} +static bool erase(void *user, uint32_t size) +{ + uint32_t address, step; + bool ok = true; (void)user; + if (size < 8 || size > G474_APP_SIZE || !geometry_valid()) return false; + g474_writer_init(&writer, size); + if (HAL_FLASH_Unlock() != HAL_OK) return false; + __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_ALL_ERRORS); + step = (FLASH->OPTR & FLASH_OPTR_DBANK) ? 2048U : 4096U; + for (address = G474_APP_BASE; address < G474_APP_BASE + size; address += step) { + if (!erase_page(address)) { ok = false; break; } + } + (void)HAL_FLASH_Lock(); return ok; +} +static bool program_word(void *user, uint32_t offset, uint64_t word) +{ + uint64_t actual; (void)user; + if ((offset & 7U) || offset > G474_APP_SIZE - 8U) return false; + if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, G474_APP_BASE + offset, + word) != HAL_OK) return false; + memcpy(&actual, (const void *)(G474_APP_BASE + offset), 8); + return actual == word; +} +static bool write(void *user, uint32_t offset, const uint8_t *data, uint16_t length) +{ + bool ok; (void)user; + if (HAL_FLASH_Unlock() != HAL_OK) return false; + __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_ALL_ERRORS); + ok = g474_writer_write(&writer, offset, data, length, program_word, 0); + (void)HAL_FLASH_Lock(); return ok; +} +static bool verify(void *user, uint32_t size, uint32_t crc) +{ + (void)user; + return !writer.failed && !writer.used && writer.received == size && + writer.size == size && + g474_vectors_valid((const uint8_t *)G474_APP_BASE, size) && + setp_crc32((const uint8_t *)G474_APP_BASE, size) == crc; +} +static bool commit(void *user, uint32_t size, uint32_t crc) +{ + uint32_t record[4] = {size, crc, ~size, COMMIT}; + uint64_t word; unsigned i; bool ok = true; (void)user; + if (HAL_FLASH_Unlock() != HAL_OK) return false; + __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_ALL_ERRORS); + for (i = 0; i < sizeof(record); i += 8) { + memcpy(&word, (const uint8_t *)record + i, 8); + if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, G474_META_BASE + i, + word) != HAL_OK) { ok = false; break; } + } + (void)HAL_FLASH_Lock(); + return ok && !memcmp((const void *)G474_META_BASE, record, sizeof(record)); +} +bool g474_app_valid(void) +{ + /* Same reset policy as F407: SWD images need no USB commit record. */ + return geometry_valid() && + g474_vectors_valid((const uint8_t *)G474_APP_BASE, G474_APP_SIZE); +} +static void activate(void *user) { (void)user; } +const setp_boot_ops g474_boot_ops = {invalidate, erase, write, verify, commit, activate}; +__attribute__((naked,noreturn)) static void enter(uint32_t sp, uint32_t pc) +{ + __asm volatile("msr msp, r0\nmovs r2, #0\nmsr control, r2\ndsb\nisb\ncpsie i\nbx r1\n"); +} +void g474_jump_app(void) +{ + unsigned i; + uint32_t sp = *(const uint32_t *)G474_APP_BASE; + uint32_t pc = *(const uint32_t *)(G474_APP_BASE + 4); + __disable_irq(); SysTick->CTRL = 0; + for (i = 0; i < 8; ++i) { NVIC->ICER[i] = 0xffffffff; NVIC->ICPR[i] = 0xffffffff; } + SCB->ICSR = SCB_ICSR_PENDSTCLR_Msk | SCB_ICSR_PENDSVCLR_Msk; + SCB->VTOR = G474_APP_BASE; enter(sp, pc); +} diff --git a/c/set-protocol/ports/stm32g474-usb-boot/boot_image.h b/c/set-protocol/ports/stm32g474-usb-boot/boot_image.h new file mode 100644 index 0000000..6a4e2ba --- /dev/null +++ b/c/set-protocol/ports/stm32g474-usb-boot/boot_image.h @@ -0,0 +1,34 @@ +#ifndef G474_BOOT_IMAGE_H +#define G474_BOOT_IMAGE_H +#include "boot_port.h" +#include +/* SRAM1+SRAM2: 96 KB; CCM: 32 KB, also aliased after SRAM2. */ +static inline bool g474_vectors_valid(const uint8_t *image, uint32_t size) +{ + uint32_t sp, pc; + if (size < 8 || size > G474_APP_SIZE) return false; + memcpy(&sp, image, 4); memcpy(&pc, image + 4, 4); + return !(sp & 7U) && + ((sp > 0x20000000UL && sp <= 0x20020000UL) || + (sp > 0x10000000UL && sp <= 0x10008000UL)) && + (pc & 1U) && (pc & ~1UL) >= G474_APP_BASE && + (pc & ~1UL) < G474_APP_BASE + size; +} +/* A contiguous xB slot at 0x08010000 exists only with DBANK=0. + * In dual mode the upper 64 KB is at 0x08040000, not at this slot address. */ +static inline bool g474_geometry_supported(uint16_t flash_kb, bool dual, bool swapped) +{ + return flash_kb == 128U && !dual && !swapped; +} +static inline bool g474_flash_page(uint32_t address, bool dual, + uint32_t *bank, uint32_t *page) +{ + uint32_t offset; + if (dual || address < G474_META_BASE || address >= G474_APP_BASE + G474_APP_SIZE) + return false; + offset = address - 0x08000000UL; + *bank = 1U; + *page = offset / 4096U; + return true; +} +#endif diff --git a/c/set-protocol/ports/stm32g474-usb-boot/boot_main.c b/c/set-protocol/ports/stm32g474-usb-boot/boot_main.c new file mode 100644 index 0000000..8897565 --- /dev/null +++ b/c/set-protocol/ports/stm32g474-usb-boot/boot_main.c @@ -0,0 +1,39 @@ +#include "stm32g4xx_hal.h" +#include "boot_port.h" +#include "usb_cdc.h" +static volatile uint32_t clock_fault; +void SysTick_Handler(void) { HAL_IncTick(); } +static void fatal(uint32_t code) { clock_fault = code; for (;;) {} } +static void usb_clock_init(void) +{ + RCC_OscInitTypeDef osc = {0}; + RCC_PeriphCLKInitTypeDef peripheral = {0}; + RCC_CRSInitTypeDef crs = {0}; + /* CPU stays on reset HSI16. USB uses HSI48 disciplined by USB SOF. */ + osc.OscillatorType = RCC_OSCILLATORTYPE_HSI48; + osc.HSI48State = RCC_HSI48_ON; + if (HAL_RCC_OscConfig(&osc) != HAL_OK) fatal(2); + peripheral.PeriphClockSelection = RCC_PERIPHCLK_USB; + peripheral.UsbClockSelection = RCC_USBCLKSOURCE_HSI48; + if (HAL_RCCEx_PeriphCLKConfig(&peripheral) != HAL_OK) fatal(3); + __HAL_RCC_CRS_CLK_ENABLE(); + crs.Prescaler = RCC_CRS_SYNC_DIV1; + crs.Source = RCC_CRS_SYNC_SOURCE_USB; + crs.Polarity = RCC_CRS_SYNC_POLARITY_RISING; + crs.ReloadValue = __HAL_RCC_CRS_RELOADVALUE_CALCULATE(48000000U, 1000U); + crs.ErrorLimitValue = RCC_CRS_ERRORLIMIT_DEFAULT; + crs.HSI48CalibrationValue = RCC_CRS_HSI48CALIBRATION_DEFAULT; + HAL_RCCEx_CRSConfig(&crs); +} +void boot_protocol_init(void); +void boot_protocol_poll(uint32_t now); +int main(void) +{ + bool requested; + SCB->VTOR = FLASH_BASE; + requested = g474_boot_requested(); + if (!requested && g474_app_valid()) g474_jump_app(); + if (HAL_Init() != HAL_OK) fatal(1); + usb_clock_init(); boot_protocol_init(); usb_cdc_init(); + for (;;) { uint32_t now = HAL_GetTick(); usb_cdc_poll(now); boot_protocol_poll(now); } +} diff --git a/c/set-protocol/ports/stm32g474-usb-boot/boot_port.h b/c/set-protocol/ports/stm32g474-usb-boot/boot_port.h new file mode 100644 index 0000000..9328a62 --- /dev/null +++ b/c/set-protocol/ports/stm32g474-usb-boot/boot_port.h @@ -0,0 +1,15 @@ +#ifndef G474_BOOT_PORT_H +#define G474_BOOT_PORT_H +#include "set_boot.h" +/* STM32G474xB (128 KB), single-bank mode, normal bank mapping. */ +#define G474_APP_BASE 0x08010000UL +#define G474_APP_SIZE 0x00010000UL +#define G474_META_BASE 0x0800F000UL +#define G474_BOOT_REQUEST 0x1003U +extern const setp_boot_ops g474_boot_ops; +bool g474_app_valid(void); +bool g474_boot_requested(void); +void g474_request_boot(uint32_t now); +void g474_reset_poll(uint32_t now); +void g474_jump_app(void); +#endif diff --git a/c/set-protocol/ports/stm32g474-usb-boot/boot_request.c b/c/set-protocol/ports/stm32g474-usb-boot/boot_request.c new file mode 100644 index 0000000..5c00e6c --- /dev/null +++ b/c/set-protocol/ports/stm32g474-usb-boot/boot_request.c @@ -0,0 +1,26 @@ +#include "boot_port.h" +#include "stm32g4xx_hal.h" +#define REQUEST_MAGIC 0x42555342UL +static uint32_t reset_at; +static bool reset_pending; +static void backup_access(void) +{ + __HAL_RCC_PWR_CLK_ENABLE(); + HAL_PWR_EnableBkUpAccess(); + __HAL_RCC_RTCAPB_CLK_ENABLE(); +} +bool g474_boot_requested(void) +{ + bool requested; + backup_access(); requested = TAMP->BKP0R == REQUEST_MAGIC; + TAMP->BKP0R = 0; return requested; +} +void g474_request_boot(uint32_t now) +{ + backup_access(); TAMP->BKP0R = REQUEST_MAGIC; + reset_at = now + 500; reset_pending = true; +} +void g474_reset_poll(uint32_t now) +{ + if (reset_pending && (int32_t)(now - reset_at) >= 0) NVIC_SystemReset(); +} diff --git a/c/set-protocol/ports/stm32g474-usb-boot/boot_usb_port.c b/c/set-protocol/ports/stm32g474-usb-boot/boot_usb_port.c new file mode 100644 index 0000000..8e9c615 --- /dev/null +++ b/c/set-protocol/ports/stm32g474-usb-boot/boot_usb_port.c @@ -0,0 +1,94 @@ +/* STM32G474 USB FS device, dedicated PA11/PA12 pins, single-buffer PMA. */ +#include "usbd_core.h" +#include "usbd_cdc.h" + +static PCD_HandleTypeDef pcd; +/* Единственный статический объект CDC вместо heap. Контракт предполагает один экземпляр класса. */ +static USBD_CDC_HandleTypeDef class_memory; +/* Проверяет размер запроса и возвращает статический объект либо NULL. Не является универсальным malloc. */ +void *BoardUsb_Alloc(uint32_t size) { return size<=sizeof(class_memory)?&class_memory:NULL; } +/* Освобождение не требуется: объект существует до перезапуска MCU. */ +void BoardUsb_Free(void *ptr) { (void)ptr; } +/* Согласует enum HAL и USB: OK -> OK, BUSY -> BUSY, остальные результаты -> FAIL. */ +static USBD_StatusTypeDef status(HAL_StatusTypeDef s) { + return s==HAL_OK?USBD_OK:s==HAL_BUSY?USBD_BUSY:USBD_FAIL; +} +/* Единственная точка входа USB ISR; события декодирует HAL_PCD_IRQHandler. */ +void USB_LP_IRQHandler(void) { HAL_PCD_IRQHandler(&pcd); } +/* Передаёт 8-байтовый SETUP control-запрос стандартному обработчику USB Device. */ +void HAL_PCD_SetupStageCallback(PCD_HandleTypeDef *h) { (void)USBD_LL_SetupStage(h->pData,(uint8_t *)h->Setup); } +/* OUT: хост отправил данные устройству; передаём номер endpoint и его принятый буфер. */ +void HAL_PCD_DataOutStageCallback(PCD_HandleTypeDef *h,uint8_t ep) { (void)USBD_LL_DataOutStage(h->pData,ep,h->OUT_ep[ep].xfer_buff); } +/* IN: завершена передача данных от устройства к хосту; уведомляем USB Device. */ +void HAL_PCD_DataInStageCallback(PCD_HandleTypeDef *h,uint8_t ep) { (void)USBD_LL_DataInStage(h->pData,ep,h->IN_ep[ep].xfer_buff); } +/* Передаёт событие начала USB-кадра; в текущей конфигурации SOF-прерывания отключены. */ +void HAL_PCD_SOFCallback(PCD_HandleTypeDef *h) { (void)USBD_LL_SOF(h->pData); } +/* При reset шины фиксируем Full Speed и сбрасываем состояние USB Device. */ +void HAL_PCD_ResetCallback(PCD_HandleTypeDef *h) { + (void)USBD_LL_SetSpeed(h->pData,USBD_SPEED_FULL); (void)USBD_LL_Reset(h->pData); +} +/* Сообщает middleware о приостановке шины; аппаратный low-power режим здесь не включается. */ +void HAL_PCD_SuspendCallback(PCD_HandleTypeDef *h) { (void)USBD_LL_Suspend(h->pData); } +/* Сообщает middleware о возобновлении обмена после suspend. */ +void HAL_PCD_ResumeCallback(PCD_HandleTypeDef *h) { (void)USBD_LL_Resume(h->pData); } +/* Уведомление верхнего уровня о подключении контроллера. */ +void HAL_PCD_ConnectCallback(PCD_HandleTypeDef *h) { (void)USBD_LL_DevConnected(h->pData); } +/* Уведомление верхнего уровня об отключении контроллера. */ +void HAL_PCD_DisconnectCallback(PCD_HandleTypeDef *h) { (void)USBD_LL_DevDisconnected(h->pData); } +/* Переадресует незавершённую изохронную IN-передачу; обычный CDC использует bulk endpoints. */ +void HAL_PCD_ISOINIncompleteCallback(PCD_HandleTypeDef *h,uint8_t ep) { (void)USBD_LL_IsoINIncomplete(h->pData,ep); } +/* Переадресует незавершённую изохронную OUT-передачу для полноты порта PCD. */ +void HAL_PCD_ISOOUTIncompleteCallback(PCD_HandleTypeDef *h,uint8_t ep) { (void)USBD_LL_IsoOUTIncomplete(h->pData,ep); } + +USBD_StatusTypeDef USBD_LL_Init(USBD_HandleTypeDef *d) { + /* Dedicated USB pins: no GPIO alternate-function selection on G474. */ + __HAL_RCC_USB_CLK_ENABLE(); + pcd.Instance = USB; pcd.Init.dev_endpoints = 8; + pcd.Init.speed = PCD_SPEED_FULL; pcd.Init.phy_itface = PCD_PHY_EMBEDDED; + pcd.Init.Sof_enable = DISABLE; pcd.Init.low_power_enable = DISABLE; + pcd.Init.lpm_enable = DISABLE; pcd.Init.battery_charging_enable = DISABLE; + pcd.pData = d; d->pData = &pcd; + if (HAL_PCD_Init(&pcd) != HAL_OK) return USBD_FAIL; + /* BTABLE: 0x00..0x3f; EP0 RX/TX 64 bytes each; CDC data RX/TX 64; + * CDC notification 16. Byte offsets are disjoint within the 1 KB PMA. */ + if (HAL_PCDEx_PMAConfig(&pcd, 0x00, PCD_SNG_BUF, 0x40) != HAL_OK || + HAL_PCDEx_PMAConfig(&pcd, 0x80, PCD_SNG_BUF, 0x80) != HAL_OK || + HAL_PCDEx_PMAConfig(&pcd, CDC_OUT_EP, PCD_SNG_BUF, 0xc0) != HAL_OK || + HAL_PCDEx_PMAConfig(&pcd, CDC_IN_EP, PCD_SNG_BUF, 0x100) != HAL_OK || + HAL_PCDEx_PMAConfig(&pcd, CDC_CMD_EP, PCD_SNG_BUF, 0x140) != HAL_OK) + return USBD_FAIL; + HAL_NVIC_SetPriority(USB_LP_IRQn, 7, 0); HAL_NVIC_EnableIRQ(USB_LP_IRQn); + return USBD_OK; +} +/* Запрещает USB IRQ до деинициализации контроллера, чтобы callbacks не работали с разобранным состоянием. */ +USBD_StatusTypeDef USBD_LL_DeInit(USBD_HandleTypeDef *d) { + HAL_NVIC_DisableIRQ(USB_LP_IRQn); return status(HAL_PCD_DeInit(d->pData)); +} +/* Включает USB device через HAL после завершения настройки класса и endpoints. */ +USBD_StatusTypeDef USBD_LL_Start(USBD_HandleTypeDef *d) { return status(HAL_PCD_Start(d->pData)); } +/* Останавливает контроллер USB device; память состояния остаётся статической. */ +USBD_StatusTypeDef USBD_LL_Stop(USBD_HandleTypeDef *d) { return status(HAL_PCD_Stop(d->pData)); } +/* Открывает endpoint: ep содержит номер и направление (бит 7), type — тип, mps — максимальный пакет в байтах. */ +USBD_StatusTypeDef USBD_LL_OpenEP(USBD_HandleTypeDef *d,uint8_t ep,uint8_t type,uint16_t mps) { return status(HAL_PCD_EP_Open(d->pData,ep,mps,type)); } +/* Закрывает endpoint по USB-адресу ep, включая бит направления. */ +USBD_StatusTypeDef USBD_LL_CloseEP(USBD_HandleTypeDef *d,uint8_t ep) { return status(HAL_PCD_EP_Close(d->pData,ep)); } +/* Очищает FIFO указанного endpoint через HAL. */ +USBD_StatusTypeDef USBD_LL_FlushEP(USBD_HandleTypeDef *d,uint8_t ep) { return status(HAL_PCD_EP_Flush(d->pData,ep)); } +/* Устанавливает STALL: endpoint отклоняет неподдерживаемую операцию хоста. */ +USBD_StatusTypeDef USBD_LL_StallEP(USBD_HandleTypeDef *d,uint8_t ep) { return status(HAL_PCD_EP_SetStall(d->pData,ep)); } +/* Снимает STALL по запросу верхнего уровня USB Device. */ +USBD_StatusTypeDef USBD_LL_ClearStallEP(USBD_HandleTypeDef *d,uint8_t ep) { return status(HAL_PCD_EP_ClrStall(d->pData,ep)); } +/* Применяет адрес устройства, назначенный хостом при перечислении USB. */ +USBD_StatusTypeDef USBD_LL_SetUSBAddress(USBD_HandleTypeDef *d,uint8_t address) { return status(HAL_PCD_SetAddress(d->pData,address)); } +/* Запускает IN-передачу size байтов из buf. Буфер должен жить до callback завершения; копии здесь нет. */ +USBD_StatusTypeDef USBD_LL_Transmit(USBD_HandleTypeDef *d,uint8_t ep,uint8_t *buf,uint32_t size) { return status(HAL_PCD_EP_Transmit(d->pData,ep,buf,size)); } +/* Взводит OUT endpoint для приёма в buf ёмкостью size байтов; завершение придёт через callback PCD. */ +USBD_StatusTypeDef USBD_LL_PrepareReceive(USBD_HandleTypeDef *d,uint8_t ep,uint8_t *buf,uint32_t size) { return status(HAL_PCD_EP_Receive(d->pData,ep,buf,size)); } +/* Читает состояние endpoint из HAL handle: bit7 выбирает IN, младшие 7 бит — номер. */ +uint8_t USBD_LL_IsStallEP(USBD_HandleTypeDef *d,uint8_t ep) { + PCD_HandleTypeDef *h=d->pData; return ep&0x80?h->IN_ep[ep&0x7F].is_stall:h->OUT_ep[ep].is_stall; +} +/* Возвращает фактическое число принятых байтов последней OUT-передачи. */ +uint32_t USBD_LL_GetRxDataSize(USBD_HandleTypeDef *d,uint8_t ep) { return HAL_PCD_EP_GetRxCount(d->pData,ep); } +/* Блокирующая задержка ST middleware в миллисекундах HAL; требует работающего SysTick. */ +void USBD_LL_Delay(uint32_t ms) { HAL_Delay(ms); } diff --git a/c/set-protocol/ports/stm32g474-usb-boot/boot_usb_stream.c b/c/set-protocol/ports/stm32g474-usb-boot/boot_usb_stream.c new file mode 100644 index 0000000..1a845ff --- /dev/null +++ b/c/set-protocol/ports/stm32g474-usb-boot/boot_usb_stream.c @@ -0,0 +1,10 @@ +#include "stm32g4xx_hal.h" +#include "boot_port.h" +#include "boot_config.h" +#define BOOT_OPS g474_boot_ops +#define BOOT_APP_BASE G474_APP_BASE +#define BOOT_APP_SIZE G474_APP_SIZE +#define BOOT_NODE G474_BOOT_NODE +#define BOOT_MODEL G474_BOOT_MODEL +#define BOOT_RESET() NVIC_SystemReset() +#include "../stm32-usb-boot/boot_usb_protocol.inc" diff --git a/c/set-protocol/ports/stm32g474-usb-boot/boot_writer.h b/c/set-protocol/ports/stm32g474-usb-boot/boot_writer.h new file mode 100644 index 0000000..38d56af --- /dev/null +++ b/c/set-protocol/ports/stm32g474-usb-boot/boot_writer.h @@ -0,0 +1,37 @@ +#ifndef G474_BOOT_WRITER_H +#define G474_BOOT_WRITER_H +#include +#include +#include +/* ECC Flash must be programmed once per 64-bit word. Keep partial words in RAM + * across SET packets; pad only the final word, without changing the image CRC. */ +typedef bool (*g474_program_word)(void *user, uint32_t offset, uint64_t word); +typedef struct { + uint32_t size, received; + uint8_t pending[8]; + uint8_t used; + bool failed; +} g474_writer; +static inline void g474_writer_init(g474_writer *w, uint32_t size) +{ + memset(w, 0, sizeof(*w)); w->size = size; + memset(w->pending, 0xff, sizeof(w->pending)); +} +static inline bool g474_writer_write(g474_writer *w, uint32_t offset, + const uint8_t *data, uint16_t length, g474_program_word program, void *user) +{ + if (w->failed || !length || offset != w->received || + offset > w->size || length > w->size - offset) return false; + while (length--) { + w->pending[w->used++] = *data++; ++w->received; + if (w->used == 8 || w->received == w->size) { + uint64_t word; + uint32_t start = w->received - w->used; + memcpy(&word, w->pending, sizeof(word)); + if (!program(user, start, word)) { w->failed = true; return false; } + w->used = 0; memset(w->pending, 0xff, sizeof(w->pending)); + } + } + return true; +} +#endif diff --git a/c/set-protocol/ports/stm32g474-usb-boot/test_boot_port.c b/c/set-protocol/ports/stm32g474-usb-boot/test_boot_port.c new file mode 100644 index 0000000..b2bb928 --- /dev/null +++ b/c/set-protocol/ports/stm32g474-usb-boot/test_boot_port.c @@ -0,0 +1,97 @@ +#include "boot_image.h" +#include "boot_writer.h" +#include +#include +static uint8_t flash[1024]; +static bool touched[128]; +static bool fail_write; +static bool program(void *user, uint32_t offset, uint64_t word) +{ + (void)user; + assert(!(offset & 7U)); assert(offset <= sizeof(flash) - 8); + assert(!touched[offset / 8]); + if (fail_write) return false; + touched[offset / 8] = true; + memcpy(flash + offset, &word, 8); return true; +} +static void test_writer(void) +{ + uint8_t image[521]; + uint32_t block, size, i; + for (i = 0; i < sizeof(image); ++i) image[i] = (uint8_t)(i * 19U); + /* Every block size, all possible final-word tails, including all-FF words. */ + memset(image + 128, 0xff, 32); + for (block = 1; block <= 256; ++block) { + for (size = 512; size <= sizeof(image); ++size) { + g474_writer w; + memset(flash, 0xff, sizeof(flash)); memset(touched, 0, sizeof(touched)); + g474_writer_init(&w, size); + for (i = 0; i < size;) { + uint16_t n = (uint16_t)(size - i < block ? size - i : block); + assert(g474_writer_write(&w, i, image + i, n, program, 0)); i += n; + } + assert(!w.failed && !w.used && w.received == size); + assert(!memcmp(flash, image, size)); + for (i = size; i < sizeof(flash); ++i) assert(flash[i] == 0xff); + assert(!g474_writer_write(&w, size, image, 1, program, 0)); + } + } + { + g474_writer w; + g474_writer_init(&w, 9); + assert(!g474_writer_write(&w, 1, image, 1, program, 0)); + assert(!g474_writer_write(&w, 0, image, 10, program, 0)); + assert(!g474_writer_write(&w, 0, image, 0, program, 0)); + assert(w.received == 0); + memset(touched, 0, sizeof(touched)); fail_write = true; + assert(!g474_writer_write(&w, 0, image, 8, program, 0)); + assert(w.failed); + fail_write = false; + assert(!g474_writer_write(&w, w.received, image, 1, program, 0)); + g474_writer_init(&w, 9); + assert(g474_writer_write(&w, 0, image, 9, program, 0)); + } +} +static void check_page(uint32_t address, bool dual, uint32_t bank, uint32_t page) +{ + uint32_t b, p; + assert(g474_flash_page(address, dual, &b, &p)); + assert(b == bank && p == page); +} +int main(void) +{ + uint32_t vectors[2] = {0x20018000UL, G474_APP_BASE + 9}; + uint32_t b, p; + const uint8_t *image = (const uint8_t *)vectors; + assert(g474_geometry_supported(128, false, false)); + assert(!g474_geometry_supported(128, true, false)); + assert(!g474_geometry_supported(128, false, true)); + assert(!g474_geometry_supported(512, false, false)); + assert(!g474_geometry_supported(256, false, false)); + assert(!g474_geometry_supported(0xffff, false, false)); + check_page(G474_META_BASE, false, 1, 15); + check_page(G474_APP_BASE, false, 1, 16); + check_page(0x0801ffffUL, false, 1, 31); + assert(!g474_flash_page(G474_META_BASE - 1, false, &b, &p)); + assert(!g474_flash_page(0x08020000UL, false, &b, &p)); + assert(!g474_flash_page(0x08040000UL, false, &b, &p)); + assert(!g474_flash_page(G474_META_BASE, true, &b, &p)); + assert(!g474_flash_page(G474_APP_BASE, true, &b, &p)); + assert(g474_vectors_valid(image, G474_APP_SIZE)); + vectors[0] = 0x10008000UL; assert(g474_vectors_valid(image, 16)); + vectors[0] = 0x20020000UL; assert(g474_vectors_valid(image, 16)); + vectors[0] = 0x20020008UL; assert(!g474_vectors_valid(image, 16)); + vectors[0] = 0x10008008UL; assert(!g474_vectors_valid(image, 16)); + vectors[0] = 0x20000000UL; assert(!g474_vectors_valid(image, 16)); + vectors[0] = 0x20017fffUL; assert(!g474_vectors_valid(image, 16)); + vectors[0] = 0x20018000UL; + vectors[1] = G474_APP_BASE + 8; assert(!g474_vectors_valid(image, 16)); + vectors[1] = G474_APP_BASE - 1; assert(!g474_vectors_valid(image, 16)); + vectors[1] = G474_APP_BASE + 17; assert(!g474_vectors_valid(image, 16)); + vectors[1] = G474_APP_BASE + 9; + assert(!g474_vectors_valid(image, 7)); + assert(!g474_vectors_valid(image, G474_APP_SIZE + 1)); + test_writer(); + puts("G474: bank/page boundaries, vectors, all block sizes and ECC word tails passed"); + return 0; +} diff --git a/c/set-protocol/src/set_boot.c b/c/set-protocol/src/set_boot.c new file mode 100644 index 0000000..4b2922f --- /dev/null +++ b/c/set-protocol/src/set_boot.c @@ -0,0 +1,76 @@ +#include "set_boot.h" +#include + +void setp_boot_init(setp_boot *b, const setp_boot_ops *ops, void *user, + uint32_t base, uint32_t capacity) +{ + memset(b, 0, sizeof(*b)); b->ops=*ops; b->user=user; + b->base=base; b->capacity=capacity; + b->status.max_block_size=256; +} +static bool zero_hash(const uint8_t *p) +{ + unsigned i; for(i=0;i<32;++i) if(p[i]) return false; + return true; +} +static uint16_t command(setp_boot *b, uint16_t type, const uint8_t *p, uint16_t n) +{ + setp_fw_begin_t begin; + setp_fw_data_t data; + setp_fw_end_t end; + switch(type) { + case SETP_MSG_FW_BEGIN: + if(!setp_fw_begin_decode(p,n,&begin)) return SETP_STATUS_INVALID_LENGTH; + /* This backend implements CRC integrity only, no resume/signature/SHA. */ + if(begin.slot || begin.flags!=SETP_FW_FLAG_ERASE_SLOT || + begin.signature_length || begin.signing_key_id || !zero_hash(begin.sha256)) + return SETP_STATUS_UNSUPPORTED; + if(begin.base_address!=b->base || begin.image_size<8 || + begin.image_size>b->capacity || begin.block_size>256) + return SETP_STATUS_INVALID_ARGUMENT; + b->status.state=SETP_FW_FAILED; + if(!b->ops.invalidate(b->user) || !b->ops.erase(b->user,begin.image_size)) + return SETP_STATUS_INTERNAL; + b->crc=begin.image_crc32; b->block_size=begin.block_size; + b->status.image_size=begin.image_size; b->status.next_offset=0; + b->status.state=SETP_FW_RECEIVING; return SETP_STATUS_OK; + case SETP_MSG_FW_DATA: + if(b->status.state!=SETP_FW_RECEIVING) return SETP_STATUS_WRONG_STATE; + if(!setp_fw_data_decode(p,n,&data)) return SETP_STATUS_CRC; + if(data.flags || data.data_length>b->block_size) return SETP_STATUS_INVALID_ARGUMENT; + if(data.offset!=b->status.next_offset) return SETP_STATUS_SEQUENCE; + if(data.data_length>b->status.image_size-b->status.next_offset) + return SETP_STATUS_NO_SPACE; + if(!b->ops.write(b->user,data.offset,data.data,data.data_length)) { + b->status.state=SETP_FW_FAILED; return SETP_STATUS_INTERNAL; + } + b->status.next_offset+=data.data_length; return SETP_STATUS_OK; + case SETP_MSG_FW_END: + if(b->status.state!=SETP_FW_RECEIVING) return SETP_STATUS_WRONG_STATE; + if(!setp_fw_end_decode(p,n,&end)) return SETP_STATUS_INVALID_LENGTH; + if(!zero_hash(end.sha256)) return SETP_STATUS_UNSUPPORTED; + if(end.image_size!=b->status.image_size || end.image_crc32!=b->crc || + b->status.next_offset!=b->status.image_size) return SETP_STATUS_SEQUENCE; + b->status.state=SETP_FW_FAILED; + if(!b->ops.verify(b->user,end.image_size,b->crc)) return SETP_STATUS_VERIFY_FAILED; + if(!b->ops.commit(b->user,end.image_size,b->crc)) return SETP_STATUS_INTERNAL; + b->status.state=SETP_FW_READY; return SETP_STATUS_OK; + case SETP_MSG_FW_ABORT: + if(n) return SETP_STATUS_INVALID_LENGTH; + b->status.state=SETP_FW_IDLE; b->status.next_offset=0; + return SETP_STATUS_OK; + case SETP_MSG_FW_STATUS: + return n ? SETP_STATUS_INVALID_LENGTH : SETP_STATUS_OK; + case SETP_MSG_FW_ACTIVATE: + if(n) return SETP_STATUS_INVALID_LENGTH; + if(b->status.state!=SETP_FW_READY) return SETP_STATUS_WRONG_STATE; + b->ops.activate(b->user); return SETP_STATUS_OK; + default: return SETP_STATUS_UNSUPPORTED; + } +} +uint16_t setp_boot_command(setp_boot *b, uint16_t type, const uint8_t *p, uint16_t n) +{ + uint16_t result=command(b,type,p,n); + if(type!=SETP_MSG_FW_STATUS) b->status.last_error=result; + return result; +} diff --git a/c/set-protocol/src/set_emu_igbt.c b/c/set-protocol/src/set_emu_igbt.c new file mode 100644 index 0000000..4226e78 --- /dev/null +++ b/c/set-protocol/src/set_emu_igbt.c @@ -0,0 +1,142 @@ +#include "set_emu_igbt.h" +#include +static void output(set_igbt_emu *s) +{ + unsigned ch;s->rx=0; + if(!s->enabled)return; + for(ch=0;chchannel[ch];uint8_t bit=(uint8_t)(1u<flags&2))continue; + if(c->mode==1)level=0; + else if(c->mode==2)level=1; + else {level=(uint8_t)(c->mode==3?0:!!(s->logical_rx&bit));if(c->flags&4)level^=1;} + if(level)s->rx|=bit; + } +} +void set_igbt_init(set_igbt_emu *s) +{ + unsigned ch;memset(s,0,sizeof(*s));s->enabled=1;s->delay_ms=1;s->width_ms=1; + for(ch=0;chchannel[ch]; + c->flags=3;c->on_delay=c->off_delay=1;c->on_width=c->off_width=1; + c->tx_width=10;c->edges=3; + } +} +void set_igbt_tick(set_igbt_emu *s,uint8_t pins) +{ + unsigned ch;uint8_t changed; + for(ch=0;chpulse[ch]&&--s->pulse[ch]==0)s->injected_tx&=(uint8_t)~(1u<tx=s->input_source?s->injected_tx:pins; + for(ch=0;chchannel[ch].flags&1))s->tx&=(uint8_t)~(1u<tx^s->previous_tx);s->previous_tx=s->tx; + if(!s->enabled){output(s);return;} + for(ch=0;chchannel[ch];uint8_t bit=(uint8_t)(1u<tx&bit); + if(s->active[ch]&&(!(s->fault&bit)||c->fault_width)&&--s->active[ch]==0) + s->logical_rx&=(uint8_t)~bit; + if(s->pending[ch]&&--s->pending[ch]==0) { + s->logical_rx|=bit;s->active[ch]=s->response_width[ch]; + } + if(changed&bit) { + ++s->event_count;++c->count; + if(!(c->flags&1)||!(c->flags&2)||c->mode||!(c->edges&(on?1u:2u)))continue; + s->pending[ch]=on?c->on_delay:c->off_delay; + s->response_width[ch]=(s->fault&bit)&&c->fault_width?c->fault_width:(on?c->on_width:c->off_width); + if(!s->pending[ch]){s->logical_rx|=bit;s->active[ch]=s->response_width[ch];} + } + } + output(s); +} +unsigned set_igbt_read(void *ctx,uint16_t off,uint16_t count,uint16_t *out) +{ + set_igbt_emu *s=(set_igbt_emu *)ctx; + uint16_t w[SET_IGBT_WORDS]={0};unsigned i,ch; + if(!s||!out||!count||(uint32_t)off+count>SET_IGBT_WORDS)return 2; + w[0]=0x4947;w[1]=3;w[2]=s->enabled;w[3]=s->tx;w[4]=s->rx; + w[6]=s->fault;w[7]=s->delay_ms;w[8]=s->width_ms;w[9]=s->event_count; + w[10]=8;w[11]=1;w[12]=s->input_source;w[13]=s->injected_tx; + w[15]=SET_IGBT_CHANNEL_WORDS; + for(ch=0;chchannel[ch];unsigned b=16+ch*SET_IGBT_CHANNEL_WORDS; + w[b]=c->flags;w[b+1]=c->mode;w[b+2]=c->on_delay;w[b+3]=c->on_width; + w[b+4]=c->off_delay;w[b+5]=c->off_width;w[b+6]=c->tx_width; + w[b+7]=c->edges;w[b+8]=c->fault_width;w[b+9]=c->count; + w[b+10]=s->pending[ch];w[b+11]=s->active[ch]; + } + for(i=0;i=16) { + unsigned field=(off-16)%SET_IGBT_CHANNEL_WORDS; + set_igbt_channel *c; + ch=(off-16)/SET_IGBT_CHANNEL_WORDS;if(ch>=8)return 2;c=&s->channel[ch]; + switch(field) { + case 0:if(value>7)return 3;c->flags=value;break; + case 1:if(value>3)return 3;c->mode=value;break; + case 2:if(value>1000)return 3;c->on_delay=value;break; + case 3:if(!value||value>1000)return 3;c->on_width=value;break; + case 4:if(value>1000)return 3;c->off_delay=value;break; + case 5:if(!value||value>1000)return 3;c->off_width=value;break; + case 6:if(!value||value>60000)return 3;c->tx_width=value;break; + case 7:if(value>3)return 3;c->edges=value;break; + case 8:if(value>60000)return 3;c->fault_width=value;break; + default:return 2; + } + /* Mode/enable changes discard pending responses. */ + if(field<=1) {s->pending[ch]=s->active[ch]=0;s->logical_rx&=(uint8_t)~(1u<1)return 3; + s->enabled=(uint8_t)value; + if(!value) {s->logical_rx=0; + for(ch=0;ch<8;++ch)if(s->pulse[ch])s->injected_tx&=(uint8_t)~(1u<pending,0,sizeof(s->pending)); + memset(s->active,0,sizeof(s->active));memset(s->pulse,0,sizeof(s->pulse));} + break; + case 3:case 13: + if(value>255)return 3; + s->injected_tx=(uint8_t)value;memset(s->pulse,0,sizeof(s->pulse));break; + case 6: + if(value>255)return 3; + released=(uint8_t)(s->fault&~value);s->fault=(uint8_t)value; + s->logical_rx&=(uint8_t)~released; + for(ch=0;ch<8;++ch)if(released&(1u<active[ch]=0; + break; + case 7: + if(value>1000)return 3; + s->delay_ms=value; + for(ch=0;ch<8;++ch)s->channel[ch].on_delay=s->channel[ch].off_delay=value; + break; + case 8: + if(!value||value>255)return 3; + s->width_ms=value; + for(ch=0;ch<8;++ch)s->channel[ch].on_width=s->channel[ch].off_width=value; + break; + case 9: + if(value)return 3; + s->event_count=0;for(ch=0;ch<8;++ch)s->channel[ch].count=0;break; + case 12: + if(value>1)return 3; + s->input_source=(uint8_t)value;memset(s->pulse,0,sizeof(s->pulse));break; + case 14: + if(value>255)return 3; + if(!s->enabled||!s->input_source)return 6; + /* A pulse must start from low; retrigger without an edge is refused. */ + if(value&(s->injected_tx|s->tx))return 6; + for(ch=0;ch<8;++ch)if((value&(1u<channel[ch].flags&1))return 6; + s->injected_tx|=(uint8_t)value; + for(ch=0;ch<8;++ch)if(value&(1u<pulse[ch]=(uint16_t)(s->channel[ch].tx_width+1u); + break; + default:return 2; + } + output(s);return 0; +} diff --git a/c/set-protocol/src/set_emu_server.c b/c/set-protocol/src/set_emu_server.c new file mode 100644 index 0000000..4e5b265 --- /dev/null +++ b/c/set-protocol/src/set_emu_server.c @@ -0,0 +1,194 @@ +#include "set_emu_server.h" +#include "set_crc.h" +#include +static const uint32_t baudrates[5]={9600,19200,38400,57600,115200}; +uint32_t set_emu_server_baud(const set_emu_server *s) { return baudrates[s->config[2]]; } +static unsigned control_read(void *ctx,uint16_t off,uint16_t n,uint16_t *out) +{ + set_emu_server *s=(set_emu_server *)ctx;uint16_t w[36]={0};unsigned i; + if(!((off==0&&n==4)||(off==16&&(n==8||n==10))||(off==32&&n==4)))return 2; + w[0]=0x454d;w[1]=1;w[2]=s->ump.enabled&1u;w[3]=s->ump.prev_command; + w[16]=0x5343;w[17]=3; + for(i=0;i<3;++i){w[18+i]=s->config[i];w[21+i]=s->staged[i];} + w[24]=s->config[3];w[25]=s->staged[3]; + w[32]=0x4642;w[33]=1;w[34]=3;w[35]=SET_UMP_WORDS; + for(i=0;iconfig[3]!=0)return 6; + if(v>1)return 3; + s->ump.enabled=(uint8_t)((s->ump.enabled&~1u)|v); + if(!v)s->ump.scenario=0; + return 0; + } + if(off<21||off>25)return 2; + if(s->apply_delay)return 6; + if(off==24) { + if(v==0)memcpy(s->staged,s->config,sizeof(s->config)); + else if(v==0xa55a) { + if(s->staged[0]==s->staged[1])return 3; + s->apply_delay=500; + } else return 3; + } else if(off==25) { + if(v>3 || (v==3&&!s->wave))return 3; + s->staged[3]=v; + } else { + if((off==21&&(!v||v>247))||(off==22&&(!v||v>255))||(off==23&&v>4))return 3; + s->staged[off-21]=v; + } + return 0; +} +static unsigned ump_read(void *ctx,uint16_t off,uint16_t n,uint16_t *out) +{ + set_emu_server *s=(set_emu_server *)ctx; + return s->config[3]?6:set_ump_read(&s->ump,off,n,out); +} +static unsigned ump_write(void *ctx,uint16_t off,uint16_t v) +{ + set_emu_server *s=(set_emu_server *)ctx; + return s->config[3]?6:set_ump_write(&s->ump,off,v); +} +static unsigned indexed_read(void *ctx,uint16_t off,uint16_t n,uint16_t *out) +{ + set_emu_server *s=(set_emu_server *)ctx; + return s->config[3]?6:set_ump_indexed_read(&s->ump,off,n,out); +} +static unsigned tms_read(void *ctx,uint16_t off,uint16_t n,uint16_t *out) +{ + set_emu_server *s=(set_emu_server *)ctx; + return set_tms_read(&s->tms,off,n,out); +} +static unsigned tms_write(void *ctx,uint16_t off,uint16_t v) +{ + set_emu_server *s=(set_emu_server *)ctx; + return s->config[3]!=1?6:set_tms_write(&s->tms,off,v); +} +/* Status remains readable in every mode; only the selected service may write. + * Explicit stop commands remain available regardless of mode. */ +static unsigned igbt_write(void *ctx,uint16_t off,uint16_t v) +{ + set_emu_server *s=(set_emu_server *)ctx; + if(s->config[3]!=2 && !(off==2&&v==0))return 6; + return set_igbt_write(&s->igbt,off,v); +} +static unsigned igbt_read(void *ctx,uint16_t off,uint16_t n,uint16_t *out) +{ + return set_igbt_read(&((set_emu_server *)ctx)->igbt,off,n,out); +} +static unsigned wave_write(void *ctx,uint16_t off,uint16_t v) +{ + set_emu_server *s=(set_emu_server *)ctx; + if(s->config[3]!=3 && !(off==2&&v==0))return 6; + return set_wave_write(s->wave,off,v); +} +static unsigned wave_read(void *ctx,uint16_t off,uint16_t n,uint16_t *out) +{ + return set_wave_read(((set_emu_server *)ctx)->wave,off,n,out); +} +static unsigned samples_write(void *ctx,uint16_t off,uint16_t v) +{ + set_emu_server *s=(set_emu_server *)ctx; + return s->config[3]!=3?6:set_wave_samples_write(s->wave,off,v); +} +static unsigned samples_read(void *ctx,uint16_t off,uint16_t n,uint16_t *out) +{ + return set_wave_samples_read(((set_emu_server *)ctx)->wave,off,n,out); +} +static void region(set_reg_region *r,uint16_t base,uint16_t words,uint16_t kind, + uint16_t version,void *ctx,set_reg_read_fn read,set_reg_write_fn write) +{ + r->base=base;r->words=words;r->kind=kind;r->version=version;r->instance=0; + r->flags=(uint16_t)(SET_REG_READ|(write?SET_REG_WRITE:0)); + r->context=ctx;r->read=read;r->write=write; +} +unsigned set_emu_server_init(set_emu_server *s,uint16_t *b0,uint16_t *b1, + uint16_t capacity,set_wave_state *wave,unsigned address,unsigned tms, + unsigned baud,unsigned can,unsigned enabled) +{ + size_t n=0; + if(!s||!b0||!b1||b0==b1||capacity<2||capacity>819|| + !address||address>247||!tms||tms>255||address==tms||baud>4|| + !can||can>16||enabled>1)return 3; + if(wave&&(!wave->samples||wave->capacity<2||wave->capacity>SET_WAVE_MAX))return 3; + memset(s,0,sizeof(*s));s->wave=wave; + if(set_ump_init(&s->ump,b0,b1,capacity,(uint16_t)can,(uint8_t)enabled))return 3; + set_tms_init(&s->tms);set_igbt_init(&s->igbt);s->igbt.enabled=0; + s->config[0]=(uint16_t)address;s->config[1]=(uint16_t)tms;s->config[2]=(uint16_t)baud; + memcpy(s->staged,s->config,sizeof(s->config)); + region(&s->regions[n++],0x1000,0x17c,SET_EMU_KIND_UMP,2,s,ump_read,ump_write); + region(&s->regions[n++],SET_EMU_CONTROL_BASE,36,SET_EMU_KIND_CONTROL,3,s,control_read,control_write); + region(&s->regions[n++],SET_EMU_IGBT_BASE,SET_IGBT_WORDS,SET_EMU_KIND_IGBT,3,s,igbt_read,igbt_write); + region(&s->regions[n++],SET_EMU_TMS_BASE,SET_TMS_WORDS,SET_EMU_KIND_TMS,1,s,tms_read,tms_write); + region(&s->regions[n++],SET_EMU_INDEXED_BASE,(uint16_t)(capacity*SET_UMP_WORDS), + SET_EMU_KIND_UMP_DATA,1,s,indexed_read,0); + if(wave) { + region(&s->regions[n++],SET_WAVE_BASE,9,SET_EMU_KIND_WAVE,3,s,wave_read,wave_write); + region(&s->regions[n++],SET_WAVE_DATA,(uint16_t)wave->capacity, + SET_EMU_KIND_WAVE_DATA,1,s,samples_read,samples_write); + } + return set_regmap_init(&s->map,s->regions,n); +} +void set_emu_server_tick(set_emu_server *s,uint8_t pins) +{ + if(s->apply_delay&&--s->apply_delay==0) { + if(s->config[3]!=s->staged[3]) { + /* Stop every executor before selecting the new one. No implicit start. */ + if(s->wave) { + if(s->wave->port.stop)s->wave->port.stop(s->wave->port.context); + s->wave->running=0;s->wave->ready=0; + } + set_igbt_init(&s->igbt);s->igbt.enabled=0; + set_tms_init(&s->tms);s->ump.state=3;s->ump.post=0;s->ump.scenario=0; + s->ump.prev_command=0;s->ump.prev_output=0; + } + memcpy(s->config,s->staged,sizeof(s->config)); + } + if(s->config[3]==1)set_tms_tick(&s->tms); + else if(s->config[3]==0)set_ump_tick(&s->ump); + else if(s->config[3]==2)set_igbt_tick(&s->igbt,pins); +} +static int inactive_ump(const set_emu_server *s,uint16_t a) +{ + return s->config[3]&&((a>=0x1000&&a<0x1200)|| + (a>=SET_EMU_INDEXED_BASE&&a<(uint32_t)SET_EMU_INDEXED_BASE+s->ump.capacity*SET_UMP_WORDS)); +} +size_t set_emu_server_rtu(set_emu_server *s,const uint8_t *r,size_t n,uint8_t *out,size_t cap) +{ + if(!s||!r||!n)return 0; + if(r[0]==s->config[1]) + return s->config[3]==1?set_tms_rtu(&s->tms,s->config[1],r,n,out,cap):0; + if(n==8&&inactive_ump(s,(uint16_t)(((unsigned)r[2]<<8)|r[3])))return 0; + return set_regmap_rtu(&s->map,s->config[0],r,n,out,cap); +} +size_t set_emu_server_can(set_emu_server *s,const uint8_t *r,size_t n, + uint16_t *out,size_t cap,unsigned *token,unsigned *error) +{ + if(token)*token=0; + if(error)*error=0; + if(!s||!r||n!=8||inactive_ump(s,(uint16_t)(((unsigned)r[0]<<8)|r[1])))return 0; + return set_regmap_can(&s->map,r,n,out,cap,token,error); +} +size_t set_emu_stream_feed(set_emu_stream *st,set_emu_server *s,uint8_t byte, + uint32_t now,uint8_t *out,size_t cap) +{ + size_t length,n; + if(!st||!s||!out||cap<253)return 0; + if((uint32_t)(now-st->last_byte)>1000u||st->used>=sizeof(st->data))st->used=0; + st->last_byte=now;st->data[st->used++]=byte; + while(st->used>=8) { + length=st->data[0]==s->config[1]&&st->data[1]==65?18u:8u; + if(st->useddata,length)) { + --st->used;memmove(st->data,st->data+1,st->used);continue; + } + n=set_emu_server_rtu(s,st->data,length,out,cap); + st->used=(uint8_t)(st->used-length); + memmove(st->data,st->data+length,st->used); + if(n)return n; + } + return 0; +} diff --git a/c/set-protocol/src/set_emu_tms.c b/c/set-protocol/src/set_emu_tms.c new file mode 100644 index 0000000..e6d9908 --- /dev/null +++ b/c/set-protocol/src/set_emu_tms.c @@ -0,0 +1,96 @@ +#include "set_emu_tms.h" +#include "set_crc.h" +#include +static void analog(uint8_t *reply, unsigned index, int32_t value) +{ + uint16_t word = (uint16_t)value; + reply[30 + index*2] = (uint8_t)word; + reply[31 + index*2] = (uint8_t)(word >> 8); +} + + +void set_tms_init(set_tms_emu *s) { memset(s,0,sizeof(*s)); } +void set_tms_tick(set_tms_emu *s) +{ + int32_t wanted = s->running ? s->target : 0; + /* 10 Hz/s, signed hundredths of Hz. Independent of UMP recorder. */ + if (++s->divider < 10) return; + s->divider = 0; + if (s->frequency < wanted) s->frequency += wanted-s->frequency < 10 ? wanted-s->frequency : 10; + if (s->frequency > wanted) s->frequency -= s->frequency-wanted < 10 ? s->frequency-wanted : 10; +} + + +static void apply(set_tms_emu *s) +{ + unsigned i;uint16_t word=s->commands[0]; + for(i=0;i<6;++i)s->controls[i]=(uint8_t)s->commands[4+i]; + s->running=(uint8_t)((s->controls[0]&0x41)==0x41); + if((s->controls[0]&0x40) && (s->controls[2]&4)) + s->target=word<0x8000?(int32_t)word:(int32_t)word-65536; +} +static void snapshot(set_tms_emu *s,uint8_t *reply) +{ + memset(reply, 0, 101); + reply[0] = 0; reply[1] = 65; + /* BALZAM_7: byte10.Go; ready/Go LED; remote selector. */ + reply[11] = s->running; + reply[14] = (uint8_t)(0x0b | (s->running << 5)); + reply[15] = (uint8_t)(6 | ((s->controls[0] & 0x40) ? 0x20 : 0)); + reply[16] = (uint8_t)(3 | ((s->controls[4] & 0xc0) >> 3)); + analog(reply, 0, s->frequency); analog(reply, 1, 2000); + analog(reply, 4, s->frequency); analog(reply, 5, s->frequency); + analog(reply, 6, 540); analog(reply, 7, 540); + analog(reply, 8, s->frequency / 10); analog(reply, 9, s->frequency / 10); + analog(reply, 10, s->frequency); analog(reply, 11, s->frequency < 0 ? -1 : 1); + analog(reply, 12, 540); analog(reply, 13, 540); + analog(reply, 14, s->frequency); analog(reply, 15, s->frequency); + analog(reply, 25, 25); analog(reply, 26, 30); + analog(reply, 28, 400); analog(reply, 29, 400); + analog(reply, 30, (s->controls[4] & 0x40) ? 120 : 40); + analog(reply, 31, (s->controls[4] & 0x40) ? 120 : 40); + analog(reply, 32, (s->controls[4] & 0x80) ? 120 : 40); + analog(reply, 33, (s->controls[4] & 0x80) ? 120 : 40); +} +unsigned set_tms_read(void *ctx,uint16_t off,uint16_t count,uint16_t *out) +{ + set_tms_emu *s=(set_tms_emu *)ctx;uint8_t r[101];unsigned i,a; + if(!s || !out || !count || (uint32_t)off+count>SET_TMS_WORDS)return 2; + snapshot(s,r); + for(i=0;irunning; + else if(a==3)v=(uint16_t)s->frequency; + else if(a==4)v=(uint16_t)s->target; + else if(a>=8 && a<18)v=s->commands[a-8]; + else if(a>=32 && a<46)v=(uint16_t)(r[2+(a-32)*2]|((unsigned)r[3+(a-32)*2]<<8)); + else if(a>=46)v=(uint16_t)(r[30+(a-46)*2]|((unsigned)r[31+(a-46)*2]<<8)); + out[i]=v; + } + return 0; +} +unsigned set_tms_write(void *ctx,uint16_t off,uint16_t v) +{ + set_tms_emu *s=(set_tms_emu *)ctx; + if(!s)return 3; + if(off>=8 && off<18) { + if(off>=12 && v>255)return 3; + s->commands[off-8]=v;return 0; + } + if(off==18) { if(v!=1)return 3;apply(s);return 0; } + return 2; +} +size_t set_tms_rtu(set_tms_emu *s,unsigned device,const uint8_t *r,size_t n, + uint8_t *out,size_t cap) +{ + unsigned i;uint16_t crc; + if(!s || !r || !out || n!=18 || cap<101 || !device || device>255 || + r[0]!=device || r[1]!=65 || set_crc16_modbus(r,n))return 0; + for(i=0;i<4;++i)s->commands[i]=(uint16_t)(r[2+2*i]|((unsigned)r[3+2*i]<<8)); + for(i=0;i<6;++i)s->commands[4+i]=r[10+i]; + apply(s);snapshot(s,out);out[0]=(uint8_t)device; + crc=set_crc16_modbus(out,98);out[98]=(uint8_t)crc;out[99]=(uint8_t)(crc>>8); + return 101; +} diff --git a/c/set-protocol/src/set_emu_ump.c b/c/set-protocol/src/set_emu_ump.c new file mode 100644 index 0000000..8e67f76 --- /dev/null +++ b/c/set-protocol/src/set_emu_ump.c @@ -0,0 +1,172 @@ +/* UMP synthetic signals and recorder. Storage belongs to the caller. */ +#include "set_emu_ump.h" +#include +static int triangle(uint32_t t, unsigned cycle) +{ + int x = (int)(t % cycle) * 480 / (int)cycle; + return x < 240 ? x - 120 : 360 - x; +} + +static void signals(set_ump_emu *s, uint16_t *p) +{ + unsigned enabled = (s->enabled & 1u) != 0; + unsigned t = enabled ? s->scenario % 6000u : 0, ch; + unsigned run = t >= 1000 && t < 5000, reset = t >= 5000 && t < 5300; + unsigned ramp = t < 1000 ? 0 : (t < 3500 ? t - 1000 : 2500); + unsigned current = run ? 60 + ramp * 100 / 2500 : + (t >= 5000 && t < 5300 ? 40 + (5300-t)*120/300 : 40); + memset(p, 0, SET_UMP_WORDS * sizeof(*p)); + p[0] = (uint16_t)s->elapsed; p[1] = (uint16_t)(s->elapsed >> 16); + p[3] = (uint16_t)((run << 7) | (reset << 6)); + p[4] = (uint16_t)((t >= 1200 && t < 5100) | (run << 1) | (reset << 2)); + p[5] = (uint16_t)((t >= 1000 && t < 2000) | ((t >= 5000) << 1) | (run << 2)); + p[6] = (uint16_t)((t >= 1000 && t < 2000) ? 2000-t : (t >= 5000 ? 6000-t : 0)); + p[7] = (uint16_t)(enabled && t < 250 ? 250-t : 0); + p[8] = (uint16_t)(500 + (current-40)*3000/160); p[9] = (uint16_t)current; + for (ch = 0; ch < 4; ++ch) { + int v = enabled ? triangle(s->scenario + ch * 211u, 720 + ch * 280u) : 0; + p[10+ch] = (uint16_t)v; + p[14+ch] = (uint16_t)((t >= 5100 && t < 5400) ? 1u << ch : 0); + p[36+ch] = (uint16_t)(2048 + v * 4); + } + /* These fields are synthetic CAN diagnostics, not transport counters. */ + p[18] = 0xE07F; p[19] = p[3]; p[20] = 6; p[21] = 16; + p[22] = enabled ? (uint16_t)(s->scenario/100) : 0; + p[24] = 6; p[25] = 16; p[26] = 500; p[27] = 3500; + p[28] = 2000; p[29] = 500; p[30] = 3500; + p[31] = (uint16_t)(run ? ramp : 0); p[32] = 2500; + p[33] = 60; p[34] = 160; p[35] = (uint16_t)(ramp*1000/2500); +} + +static void capture(set_ump_emu *s, uint16_t reason, uint16_t *p) +{ + if (reason != 5 && !s->post && (((p[3]&0x40) && !(s->prev_command&0x40)) || + (!(p[3]&0x80) && (s->prev_command&0x80)))) { + if (s->used >= s->capacity/2) s->used = s->capacity/2-1; + s->post = 1000; s->dropped = 0; reason |= 0x100; + } + s->prev_command = p[3]; s->prev_input = p[4]; s->prev_output = p[5]; + if (s->post && s->used >= s->capacity-1) { s->dropped = 1; return; } + p[2] = (uint16_t)(reason | ((reason == 5 && s->dropped) ? 0x200 : 0)); + memcpy(s->banks[s->bank][s->next], p, SET_UMP_WORDS*sizeof(*p)); + s->next = (uint16_t)((s->next+1)%s->capacity); + if (s->used < s->capacity) ++s->used; + ++s->sequence; +} + + +unsigned set_ump_init(set_ump_emu *s,uint16_t *bank0,uint16_t *bank1, + uint16_t capacity,uint16_t can_mode,uint8_t enabled) +{ + if(!s || !bank0 || !bank1 || bank0==bank1 || capacity<2 || + capacity>1638 || !can_mode || can_mode>16)return 3; + memset(s,0,sizeof(*s)); + s->banks[0]=(uint16_t (*)[SET_UMP_WORDS])bank0; + s->banks[1]=(uint16_t (*)[SET_UMP_WORDS])bank1; + s->capacity=capacity;s->can_mode=can_mode;s->enabled=enabled; + s->archive_bank=1;s->period=20;return 0; +} +void set_ump_tick(set_ump_emu *s) +{ + uint16_t p[SET_UMP_WORDS],old_post; + if (!(s->enabled & 1u)) s->scenario = 0; + if (s->state != 1) return; + ++s->elapsed; + if (s->enabled & 1u) ++s->scenario; + old_post = s->post; + signals(s,p); + if (++s->divider >= s->period) { s->divider = 0; capture(s,1,p); } + if (p[3] != s->prev_command || p[4] != s->prev_input || p[5] != s->prev_output) capture(s,3,p); + /* Count a full 1000 ms AFTER the trigger; never extend on new edges. */ + if (old_post && --s->post == 0) { + capture(s,5,p); + s->archive_bank = s->bank; s->archive_count = s->used; s->archive_next = s->next; + ++s->generation; s->bank ^= 1; s->used = s->next = 0; + } +} + +static uint16_t record_word(set_ump_emu *s, unsigned archived, unsigned record, unsigned word) +{ + unsigned n = archived ? s->archive_count : s->used; + unsigned end = archived ? s->archive_next : s->next; + if (record >= n) return 0; + return s->banks[archived ? s->archive_bank : s->bank][(end+s->capacity-n+record)%s->capacity][word]; +} + + +static uint16_t read_word(set_ump_emu *s,uint16_t address) +{ + unsigned offset,count=s->used<3?s->used:3; + uint32_t first=s->sequence-count; + switch(address) { + case 0x1000: return 0x554D; case 0x1001: return 2; + case 0x1002: return s->source ? 3 : s->state; + case 0x1003: return s->source ? s->archive_count : s->used; + case 0x1004: return SET_UMP_WORDS; case 0x1005: return s->capacity; + case 0x1006: return s->period; case 0x1007: return (uint16_t)s->elapsed; + case 0x1008: return (uint16_t)(s->elapsed>>16); case 0x1009: return 6; + case 0x100A: return s->can_mode; case 0x100B: return 1000; + case 0x100C: return s->generation; case 0x100D: return s->archive_count; + case 0x100E: return s->post; case 0x100F: return s->state; + } + if (address >= 0x1010u && address < 0x1010u+120) { + if (!s->source && s->state == 1) return 0; + offset = address-0x1010u; + return record_word(s,s->source, s->cursor+offset/SET_UMP_WORDS, offset%SET_UMP_WORDS); + } + offset = address-0x1100u; + if (offset == 0) return (uint16_t)first; + if (offset == 1) return (uint16_t)(first>>16); + if (offset == 2) return (uint16_t)count; + if (offset == 3) return s->generation; + offset -= 4; + return offset/SET_UMP_WORDS < count ? record_word(s,0,s->used-count+offset/SET_UMP_WORDS,offset%SET_UMP_WORDS) : 0; +} + +unsigned set_ump_read(void *ctx,uint16_t off,uint16_t count,uint16_t *out) +{ + set_ump_emu *s=(set_ump_emu *)ctx;unsigned i; + uint32_t end=(uint32_t)off+count; + if(!s || !out || !count || count>124)return 3; + if(!((off<16 && end<=16) || (off>=0x10 && end<=0x88) || + (off==0x100 && count==124)))return 2; + for(i=0;i120 || off%SET_UMP_WORDS || count%SET_UMP_WORDS)return 3; + records=s->source?s->archive_count:s->used; + if(off/SET_UMP_WORDS+count/SET_UMP_WORDS>records)return 2; + if(!s->source && s->state==1)return 6; + for(i=0;isource,(off+i)/SET_UMP_WORDS,(off+i)%SET_UMP_WORDS); + return 0; +} +unsigned set_ump_write(void *ctx,uint16_t off,uint16_t value) +{ + set_ump_emu *s=(set_ump_emu *)ctx; + uint16_t address=(uint16_t)(0x1000u+off),p[SET_UMP_WORDS]; + if(!s || off>3)return 2; + if (address < 0x1000u || address > 0x1000u+3) return 2; + switch (address) { + case 0x1000u: + if (value == 0) { s->state = 3; s->post = 0; } + else if (value == 1 && s->state != 1) { + s->used = s->next = s->cursor = s->divider = s->post = s->source = s->dropped = 0; + s->scenario = 0; s->state = 1; + signals(s,p); s->prev_command = p[3]; capture(s,1,p); + } else return 3; + break; + case 0x1000u+1: + if (s->state == 1 || value < 1 || value > 1000) return 3; + s->period = value; break; + case 0x1000u+2: + if (value >= s->capacity) return 3; + s->cursor = value; break; + case 0x1000u+3: + if (value > 1) return 3; + s->source = value; break; + } + return 0; +} diff --git a/c/set-protocol/src/set_regmap.c b/c/set-protocol/src/set_regmap.c new file mode 100644 index 0000000..89dbc40 --- /dev/null +++ b/c/set-protocol/src/set_regmap.c @@ -0,0 +1,117 @@ +#include "set_regmap.h" +#include "set_crc.h" +#include +static uint16_t get16(const uint8_t *p) { return (uint16_t)(((unsigned)p[0]<<8)|p[1]); } +static void put16(uint8_t *p, unsigned v) { p[0]=(uint8_t)(v>>8); p[1]=(uint8_t)v; } +unsigned set_regmap_init(set_regmap *m, const set_reg_region *r, size_t count) +{ + size_t i,j; + if (!m) return SET_REG_VALUE; + m->regions=0; m->count=0; + if ((!r && count) || count>64) return SET_REG_VALUE; + for (i=0;i65536u || r[i].base<8u+count*8u || + !r[i].flags || (r[i].flags & ~3u) || + ((r[i].flags&SET_REG_READ) && !r[i].read) || + ((r[i].flags&SET_REG_WRITE) && !r[i].write)) return SET_REG_VALUE; + for (j=0;jregions=r; m->count=count; return 0; +} +static uint16_t directory(const set_regmap *m, unsigned a) +{ + const set_reg_region *r; + if (a==0) return 0x5345; /* SE: service directory */ + if (a==1) return 1; + if (a==2) return (uint16_t)m->count; + if (a==3) return 8; + if (a==4) return 8; + if (a==5) return SET_REG_MAX_WORDS; + if (a<8) return 0; + r=&m->regions[(a-8)/8]; + switch ((a-8)%8) { + case 0:return r->kind; case 1:return r->version; case 2:return r->instance; + case 3:return r->base; case 4:return r->words; case 5:return r->flags; + default:return 0; + } +} +unsigned set_regmap_request(const set_regmap *m, unsigned fn, uint16_t a, + uint16_t v, uint16_t *out, size_t cap, size_t *count) +{ + size_t i, n; unsigned error; + uint32_t end; + if (!count) return SET_REG_VALUE; + *count=0; + if (!m || (!m->regions && m->count) || !out) return SET_REG_VALUE; + if (fn!=3 && fn!=6) return SET_REG_FUNCTION; + n=fn==3?v:2; + if (!n || n>SET_REG_MAX_WORDS || cap65536u) return SET_REG_ADDRESS; + if (a<8u+8u*m->count) { + if (fn!=3 || end>8u+8u*m->count) return SET_REG_ADDRESS; + for(i=0;icount;++i) { + const set_reg_region *r=&m->regions[i]; + if (abase || a>=(uint32_t)r->base+r->words) continue; + if (end>(uint32_t)r->base+r->words) return SET_REG_ADDRESS; + if (fn==3) { + if (!(r->flags&SET_REG_READ)) return SET_REG_ADDRESS; + error=r->read(r->context,(uint16_t)(a-r->base),v,out); + } else { + if (!(r->flags&SET_REG_WRITE)) return SET_REG_ADDRESS; + error=r->write(r->context,(uint16_t)(a-r->base),v); + if (!error) { out[0]=a; out[1]=v; } + } + if (!error) *count=n; + return error; + } + return SET_REG_ADDRESS; +} +size_t set_regmap_rtu(const set_regmap *m, unsigned device, const uint8_t *req, + size_t length, uint8_t *out, size_t cap) +{ + uint16_t words[SET_REG_MAX_WORDS],crc; size_t count=0,n,i; + unsigned error; + if (!req || !out || length!=8 || cap<253 || device<1 || device>247 || + req[0]!=device || req[1]>127 || set_crc16_modbus(req,length)) return 0; + error=set_regmap_request(m,req[1],get16(req+2),get16(req+4),words,SET_REG_MAX_WORDS,&count); + out[0]=(uint8_t)device;out[1]=req[1]; + if (error) { out[1]|=0x80;out[2]=(uint8_t)error;n=3; } + else if (req[1]==6) { put16(out+2,words[0]);put16(out+4,words[1]);n=6; } + else { + out[2]=(uint8_t)(2*count); + for(i=0;i>8); + return n; +} +size_t set_regmap_can(const set_regmap *m, const uint8_t *r, size_t length, + uint16_t *out, size_t cap, unsigned *token, unsigned *error) +{ + size_t n=0; + if (token) *token=0; + if (error) *error=0; + if (!r || !out || !token || !error || length!=8 || capSET_REG_MAX_WORDS || (n&1) || + index>=n/2 || !token || token>255 || error>255) return 0; + put16(out,w[index*2]);put16(out+2,w[index*2+1]); + put16(out+4,0x1f00u+(unsigned)index);out[6]=(uint8_t)token;out[7]=(uint8_t)error; + return 8; +} diff --git a/c/set-protocol/src/set_wavegen.c b/c/set-protocol/src/set_wavegen.c new file mode 100644 index 0000000..b713c47 --- /dev/null +++ b/c/set-protocol/src/set_wavegen.c @@ -0,0 +1,214 @@ +#include "set_wavegen.h" +#include "set_crc.h" +#include +static uint16_t read16(const uint8_t *p) {return (uint16_t)((p[0]<<8)|p[1]);} +static void write16(uint8_t *p,unsigned value) {p[0]=(uint8_t)(value>>8);p[1]=(uint8_t)value;} +static size_t seal(uint8_t *p,size_t n) { + uint16_t crc=set_crc16_modbus(p,n);p[n]=(uint8_t)crc;p[n+1]=(uint8_t)(crc>>8);return n+2; +} +void set_wave_init(set_wave_state *s,uint16_t *samples,size_t capacity,const set_wave_port *port) { + memset(s,0,sizeof(*s));s->samples=samples;s->capacity=capacity;s->rate=1000; + if(port) s->port=*port; +} +static unsigned write_register(set_wave_state *s,unsigned reg,unsigned value) { + if(reg==SET_WAVE_BASE+2 && value==0) { + if(s->port.stop) s->port.stop(s->port.context); + s->running=0; return 0; + } + if(s->running) return 6; + if(reg==SET_WAVE_BASE+2 && value==1) { + if(!s->ready||!s->port.start) return 3; + if(s->port.start(s->port.context,s->samples,s->count,s->rate)) return 4; + s->running=1;return 0; + } + if(reg==SET_WAVE_BASE+3) { + uint32_t rate=((uint32_t)s->rate_high<<16)|value; + if(rate<1||rate>SET_WAVE_MAX_RATE) return 3; + s->rate=rate;s->ready=0;return 0; + } + if(reg==SET_WAVE_BASE+8) { + if(value>(SET_WAVE_MAX_RATE>>16)) return 3; + s->rate_high=(uint16_t)value;s->ready=0;return 0; + } + if(reg==SET_WAVE_BASE+4) { + if(value<2||value>SET_WAVE_MAX||value>s->capacity) return 3; + s->count=(uint16_t)value;s->received=s->ready=0;return 0; + } + if(reg>=SET_WAVE_DATA&®=s->count||index>s->received||value>4095||s->ready) return 3; + if(indexreceived) return s->samples[index]==value?0:3; + s->samples[index]=(uint16_t)value;++s->received;return 0; + } + if(reg==SET_WAVE_BASE+6) { + if(value!=0xA55A||s->count<2||s->received!=s->count) return 3; + s->ready=1;return 0; + } + return 2; +} +unsigned set_wave_read(void *ctx,uint16_t off,uint16_t count,uint16_t *out) { + set_wave_state *s=(set_wave_state *)ctx;uint16_t w[9];unsigned i; + if(!s||!s->samples||!out||!count||(uint32_t)off+count>9)return 2; + w[0]=0x5747;w[1]=3;w[2]=s->running;w[3]=(uint16_t)s->rate; + w[4]=s->count;w[5]=s->received;w[6]=s->ready;w[7]=(uint16_t)s->capacity;w[8]=(uint16_t)(s->rate>>16); + for(i=0;isamples||off>=9)return 2; + return write_register(s,SET_WAVE_BASE+off,value); +} +unsigned set_wave_samples_read(void *ctx,uint16_t off,uint16_t count,uint16_t *out) { + set_wave_state *s=(set_wave_state *)ctx;unsigned i; + if(!s||!s->samples||!out||!count||(uint32_t)off+count>s->received)return 2; + for(i=0;isamples[off+i]; + return 0; +} +unsigned set_wave_samples_write(void *ctx,uint16_t off,uint16_t value) { + set_wave_state *s=(set_wave_state *)ctx; + if(!s||!s->samples||off>=SET_WAVE_MAX)return 2; + return write_register(s,SET_WAVE_DATA+off,value); +} +size_t set_wave_rtu(set_wave_state *s,unsigned device,const uint8_t *req,size_t n,uint8_t *out,size_t cap) { + unsigned reg,value,error=0,i;uint16_t status[8];size_t length=0; + if(!s||!s->samples||!req||!out||cap<21||n!=8||device<1||device>247 + ||req[0]!=device||set_crc16_modbus(req,n)) return 0; + reg=read16(req+2);value=read16(req+4); + if(!((reg>=SET_WAVE_BASE&®=SET_WAVE_DATA&®rate>>16);length=5; + } else if(reg>=SET_WAVE_DATA&&value==1&®-SET_WAVE_DATAreceived) { + out[2]=2;write16(out+3,s->samples[reg-SET_WAVE_DATA]);length=5; + } else error=2; + } else error=1; + if(error) {out[1]|=0x80;out[2]=(uint8_t)error;length=3;} + return seal(out,length); +} +size_t set_wave_request(unsigned device,unsigned op,unsigned index,unsigned value,uint8_t *out,size_t cap) { + unsigned reg=SET_WAVE_BASE,fn=6; + if(!out||cap<8||device<1||device>247) return 0; + switch(op) { + case 0: fn=3;value=8;break; + case 1: reg+=2;value=0;break; + case 2: reg+=3;if(value>65535)return 0;break; + case 3: reg+=4;if(value<2||value>SET_WAVE_MAX)return 0;break; + case 4: if(index>=SET_WAVE_MAX||value>4095)return 0;reg=SET_WAVE_DATA+index;break; + case 5: reg+=6;value=0xA55A;break; + case 6: reg+=2;value=1;break; + case 7: if(index>=SET_WAVE_MAX)return 0;reg=SET_WAVE_DATA+index;fn=3;value=1;break; + case 8: reg+=8;fn=3;value=1;break; + case 9: reg+=8;if(value>(SET_WAVE_MAX_RATE>>16))return 0;break; + default:return 0; + } + out[0]=(uint8_t)device;out[1]=(uint8_t)fn;write16(out+2,reg);write16(out+4,value);return seal(out,6); +} +int set_wave_response(const uint8_t *req,const uint8_t *reply,size_t size,uint16_t *words,size_t cap) { + size_t expected,i; + if(!req||!reply||!words||set_crc16_modbus(req,8))return -1; + if(size<2)return 0; + if(reply[0]!=req[0])return -1; + if(reply[1]==(req[1]|0x80)) { + if(size<5)return 0; + return size==5&&!set_crc16_modbus(reply,5)?-(100+(int)reply[2]):-1; + } + if(reply[1]!=req[1])return -1; + expected=req[1]==6?8:5+2*(size_t)read16(req+4); + if(size3)))return -2; + return (int)((expected-5)/2); +} + + +size_t set_wave_block_request(unsigned device,unsigned index,const uint16_t *values, + size_t count,uint8_t *out,size_t cap) { + size_t i,n=values?9+2*count:8; + if(!out||device<1||device>247||!count||count>SET_WAVE_BLOCK_MAX|| + index>=SET_WAVE_MAX||count>SET_WAVE_MAX-index||cap4095)return 0; + out[0]=(uint8_t)device;out[1]=values?16:3; + write16(out+2,SET_WAVE_DATA+index);write16(out+4,(unsigned)count); + if(values) { + out[6]=(uint8_t)(count*2); + for(i=0;isamples||!req||!out||cap<5||n<8||device<1||device>247|| + req[0]!=device||set_crc16_modbus(req,n))return 0; + reg=read16(req+2);count=read16(req+4); + if(reg=SET_WAVE_DATA+SET_WAVE_MAX)return 0; + index=reg-SET_WAVE_DATA;out[0]=req[0];out[1]=req[1]; + if(!count||count>SET_WAVE_BLOCK_MAX||count>SET_WAVE_MAX-index|| + index+count>s->count||index+count>s->capacity)error=3; + else if(req[1]==16) { + if(n!=9+2*(size_t)count||req[6]!=2*count)error=3; + else if(s->running)error=6; + else if(s->ready||index>s->received)error=3; + else { + /* Preflight the whole block, including replayed prefix, before writing. */ + for(i=0;i4095||(index+ireceived&&s->samples[index+i]!=value)) { + error=3;break; + } + } + if(!error) { + if(cap<8)return 0; + for(i=0;isamples[index+i]=read16(req+7+2*i); + if(index+count>s->received)s->received=(uint16_t)(index+count); + memcpy(out,req,6);length=6; + } + } + } else if(req[1]==3) { + if(n!=8||index+count>s->received)error=3; + else { + if(cap<5+2*(size_t)count)return 0; + out[2]=(uint8_t)(2*count); + for(i=0;isamples[index+i]); + length=3+2*(size_t)count; + } + } else error=1; + if(error) {out[1]|=0x80;out[2]=(uint8_t)error;length=3;} + return seal(out,length); +} +int set_wave_block_response(const uint8_t *req,size_t n,const uint8_t *reply, + size_t size,uint16_t *words,size_t cap) { + size_t expected,i;unsigned count; + if(!req||!reply||!words||n<8||set_crc16_modbus(req,n))return -1; + count=read16(req+4); + if(!count||count>SET_WAVE_BLOCK_MAX||read16(req+2)SET_WAVE_DATA+SET_WAVE_MAX)return -1; + if((req[1]==16&&(n!=9+2*(size_t)count||req[6]!=2*count))|| + (req[1]==3&&n!=8)||(req[1]!=3&&req[1]!=16))return -1; + if(size<2)return 0; + if(reply[0]!=req[0])return -1; + if(reply[1]==(req[1]|0x80)) { + if(size<5)return 0; + return size==5&&!set_crc16_modbus(reply,size)?-(100+(int)reply[2]):-1; + } + if(reply[1]!=req[1])return -1; + expected=req[1]==16?8:5+2*(size_t)count; + if(size>8; p[1] = v&255U; } +unsigned ump_validate(unsigned fn, uint16_t a, uint16_t n, int can) +{ + if (fn == 6) return a >= UMP_BASE && a <= UMP_BASE+3 ? 0 : 2; + if (fn != 3) return 1; + if (can) return ((a == UMP_BASE && n == 16) || + (a == UMP_DATA && (n == 40 || n == 80 || n == 120)) || + (a == UMP_LIVE && n == 124)) ? 0 : 2; + if (!n || n > 125) return 2; + return ((a >= UMP_BASE && a < UMP_BASE+16 && n <= UMP_BASE+16-a) || + (a >= UMP_DATA && a < UMP_DATA+120 && n <= UMP_DATA+120-a) || + (a == UMP_LIVE && n == 124)) ? 0 : 2; +} +unsigned ump_prepare(const ump_backend *b, unsigned fn, uint16_t a, + uint16_t n, int can, uint16_t *out, unsigned *error) +{ + unsigned i; + if (!out || !error) return 0; + *error = ump_validate(fn,a,n,can); + if (!*error) { + if (!b || (fn == 3 ? !b->read : !b->write)) *error = 3; + else if (fn == 3) { + for (i=0;iread(b->context,(uint16_t)(a+i)); + return n; + } else if (!b->write(b->context,a,n)) *error = 3; + } + out[0]=a; out[1]=n; + return 2; +} +uint16_t ump_crc(const ump_octet *p, size_t n) +{ + uint16_t crc=0xFFFFU; + unsigned i; + while (n--) { + crc ^= *p++ & 255U; + for (i=0;i<8;i++) crc=(uint16_t)((crc>>1)^((crc&1U)?0xA001U:0)); + } + return crc; +} +uint32_t ump_can_id(unsigned mode, unsigned kind) +{ return mode>=1 && mode<=16 && kind<=2 ? 0xBA0000UL+kind*16UL+mode-1 : 0; } +unsigned ump_can_request(const ump_backend *b, const ump_octet *r, size_t size, + uint16_t *out, unsigned *token, unsigned *error) +{ + size_t i; + if (!r || !out || !token || !error || size!=8) return 0; + for (i=0;i255) return 0; + if (get16(r+4)!=UMP_MARKER || !r[6]) return 0; + *token=r[6]; + return ump_prepare(b,r[7],get16(r),get16(r+2),1,out,error); +} +unsigned ump_can_fragment(const uint16_t *words, unsigned count, unsigned index, + unsigned token, unsigned error, ump_octet *out) +{ + if (!words || !out || count<2 || count>124 || (count&1) || + index>=count/2 || !token || token>255 || error>255) return 0; + put16(out,words[index*2]); put16(out+2,words[index*2+1]); + put16(out+4,(uint16_t)(UMP_MARKER+index)); out[6]=(ump_octet)token; out[7]=(ump_octet)error; + return 8; +} +size_t ump_rtu(const ump_backend *b, unsigned device, const ump_octet *r, + size_t size, ump_octet *out, size_t capacity) +{ + unsigned i; + if (!r || !out || size!=8 || capacity247 || r[0]!=device) return 0; + for (i=0;i<8;i++) if (r[i]>255) return 0; + if (ump_crc(r,8)) return 0; + return ump_rtu_response(b,device,r[1],get16(r+2),get16(r+4),out,capacity); +} +size_t ump_rtu_response(const ump_backend *b, unsigned device, unsigned fn, + uint16_t address, uint16_t value, ump_octet *out, size_t capacity) +{ + uint16_t words[UMP_MAX_WORDS]; + unsigned count,error; + if (!out || capacity247 || fn>127) return 0; + count=ump_prepare(b,fn,address,value,0,words,&error); + return ump_rtu_encode(device,fn,error,words,count,out,capacity); +} +size_t ump_rtu_encode(unsigned device, unsigned fn, unsigned error, + const uint16_t *words, unsigned count, ump_octet *out, size_t capacity) +{ + unsigned i; + size_t n; + uint16_t crc; + if (!out || !device || device>247 || fn>127 || error>255) return 0; + if (!error && (!words || (fn!=3 && fn!=6) || + (fn==3 && (!count || count>124)) || (fn==6 && count!=2))) return 0; + n=error ? 5 : (fn==6 ? 8 : 5+count*2); + if (capacity>8; + return n; +} diff --git a/c/set-protocol/tests/test_boot.c b/c/set-protocol/tests/test_boot.c new file mode 100644 index 0000000..a1d672f --- /dev/null +++ b/c/set-protocol/tests/test_boot.c @@ -0,0 +1,70 @@ +#include "set_boot.h" +#include +#include +#include +static uint8_t flash[1024]; +static bool valid, fail_write, fail_verify, fail_invalidate; +static unsigned erases, activations; +static bool invalidate(void *u) { (void)u; if(fail_invalidate) return false; valid=false; return true; } +static bool erase(void *u,uint32_t n) { (void)u; assert(!valid); assert(n<=sizeof(flash)); ++erases; memset(flash,255,sizeof(flash)); return true; } +static bool write(void *u,uint32_t off,const uint8_t *p,uint16_t n) { (void)u; if(fail_write) return false; memcpy(flash+off,p,n); return true; } +static bool verify(void *u,uint32_t n,uint32_t crc) { (void)u; return !fail_verify && setp_crc32(flash,n)==crc; } +static bool commit(void *u,uint32_t n,uint32_t crc) { (void)u; assert(verify(0,n,crc)); valid=true; return true; } +static void activate(void *u) { (void)u; assert(valid); ++activations; } +int main(void) { + setp_boot b; + const setp_boot_ops ops={invalidate,erase,write,verify,commit,activate}; + uint8_t image[517],p[512]; unsigned i; size_t n; + setp_fw_begin_t begin={0}; setp_fw_end_t end={0}; + for(i=0;i256) count=256; + n=setp_fw_data_encode(i,0,image+i,count,p,sizeof(p)); + assert(!setp_boot_command(&b,SETP_MSG_FW_DATA,p,(uint16_t)n)); + } + assert(!valid); n=setp_fw_end_encode(&end,p,sizeof(p)); + flash[20]^=1; + assert(setp_boot_command(&b,SETP_MSG_FW_END,p,(uint16_t)n)==SETP_STATUS_VERIFY_FAILED); + assert(!valid); + /* A reset loses the session, never promotes a partially written image. */ + setp_boot_init(&b,&ops,0,begin.base_address,sizeof(flash)); + assert(setp_boot_command(&b,SETP_MSG_FW_END,p,(uint16_t)n)==SETP_STATUS_WRONG_STATE); + n=setp_fw_begin_encode(&begin,p,sizeof(p)); assert(!setp_boot_command(&b,SETP_MSG_FW_BEGIN,p,(uint16_t)n)); + for(i=0;i256) count=256; + n=setp_fw_data_encode(i,0,image+i,count,p,sizeof(p)); + assert(!setp_boot_command(&b,SETP_MSG_FW_DATA,p,(uint16_t)n)); + } + n=setp_fw_end_encode(&end,p,sizeof(p)); assert(!setp_boot_command(&b,SETP_MSG_FW_END,p,(uint16_t)n)); + assert(valid && !memcmp(image,flash,sizeof(image))); + assert(!setp_boot_command(&b,SETP_MSG_FW_ACTIVATE,0,0)); assert(activations==1); + n=setp_fw_begin_encode(&begin,p,sizeof(p)); assert(!setp_boot_command(&b,SETP_MSG_FW_BEGIN,p,(uint16_t)n)); + fail_write=true; n=setp_fw_data_encode(0,0,image,256,p,sizeof(p)); + assert(setp_boot_command(&b,SETP_MSG_FW_DATA,p,(uint16_t)n)==SETP_STATUS_INTERNAL); + assert(!valid && b.status.state==SETP_FW_FAILED); + puts("SET boot: validation, sequence, CRC, readback, interrupted update and Flash failure passed"); + return 0; +} diff --git a/c/set-protocol/tests/test_emulator_services.c b/c/set-protocol/tests/test_emulator_services.c new file mode 100644 index 0000000..1096096 --- /dev/null +++ b/c/set-protocol/tests/test_emulator_services.c @@ -0,0 +1,233 @@ +/* Portable contract tests. Assertions stay enabled even in Release builds. */ +#include "set_emu_server.h" +#include "set_crc.h" +#include +#include +#include +static set_emu_server a,b; +static uint16_t banks[4][32*SET_UMP_WORDS],samples[2][16]; +static set_wave_state wave[2]; +static unsigned starts[2],stops[2]; +static int start(void *ctx,const uint16_t *p,size_t n,uint32_t rate) +{ + unsigned *calls=(unsigned *)ctx; + assert(p&&n>=2&&rate);++*calls;return 0; +} +static void stop(void *ctx) { unsigned *calls=(unsigned *)ctx; ++stops[calls==&starts[1]]; } +static void init(void) +{ + set_wave_port p; + memset(starts,0,sizeof(starts));memset(stops,0,sizeof(stops)); + p.context=&starts[0];p.start=start;p.stop=stop;set_wave_init(&wave[0],samples[0],16,&p); + p.context=&starts[1];set_wave_init(&wave[1],samples[1],16,&p); + assert(!set_emu_server_init(&a,banks[0],banks[1],32,&wave[0],16,1,4,5,1)); + assert(!set_emu_server_init(&b,banks[2],banks[3],32,&wave[1],23,2,0,6,0)); +} +static void write(set_emu_server *s,uint16_t address,uint16_t value) +{ + uint16_t out[2];size_t n=99; + assert(!set_regmap_request(&s->map,6,address,value,out,2,&n)); + assert(n==2&&out[0]==address&&out[1]==value); +} +static void select_mode(set_emu_server *s,unsigned mode) +{ + unsigned i;write(s,0x1219,(uint16_t)mode);write(s,0x1218,0xa55a); + for(i=0;i<500;++i)set_emu_server_tick(s,0); + assert(s->config[3]==mode); +} +static uint16_t read1(set_emu_server *s,uint16_t address) +{ + uint16_t out;size_t n=99; + assert(!set_regmap_request(&s->map,3,address,1,&out,1,&n)); + assert(n==1);return out; +} +static void seal(uint8_t *p,size_t n) +{ + uint16_t crc=set_crc16_modbus(p,n);p[n]=(uint8_t)crc;p[n+1]=(uint8_t)(crc>>8); +} +static void rtu(uint8_t *p,uint8_t device,uint8_t fn,uint16_t address,uint16_t value) +{ + p[0]=device;p[1]=fn;p[2]=(uint8_t)(address>>8);p[3]=(uint8_t)address; + p[4]=(uint8_t)(value>>8);p[5]=(uint8_t)value;seal(p,6); +} +static void directory_and_relocation(void) +{ + uint16_t out[124];size_t n;set_igbt_emu other; + set_regmap map;set_reg_region r[2]={ + {0x300,16,SET_EMU_KIND_IGBT,2,0,3,&a.igbt,set_igbt_read,set_igbt_write}, + {0x400,16,SET_EMU_KIND_IGBT,2,1,3,&other,set_igbt_read,set_igbt_write}}; + init();set_igbt_init(&other); + assert(!set_regmap_request(&a.map,3,0,64,out,124,&n)); + assert(n==64&&out[0]==0x5345&&out[1]==1&&out[2]==7&&out[5]==124); + assert(out[8]==SET_EMU_KIND_UMP&&out[11]==0x1000); + assert(!set_regmap_init(&map,r,2)); + assert(!set_regmap_request(&map,6,0x307,777,out,2,&n)); + assert(a.igbt.delay_ms==777&&other.delay_ms==1); + assert(!set_regmap_request(&map,6,0x407,9,out,2,&n)); + assert(other.delay_ms==9); + assert(set_regmap_request(&map,3,0x30f,2,out,124,&n)==2&&n==0); + assert(set_regmap_request(&map,3,0xffff,2,out,124,&n)==2); + assert(set_regmap_request(&map,6,0,1,out,124,&n)==2); + r[1].base=0x30f;assert(set_regmap_init(&map,r,2)==2&&map.count==0); + r[1].base=0xffff;assert(set_regmap_init(&map,r,2)==3); + r[1].base=8;assert(set_regmap_init(&map,r,2)==3); + r[1].base=0x400;r[1].write=0;assert(set_regmap_init(&map,r,2)==3); +} +static void capacity_and_frames(void) +{ + uint8_t req[8],reply[256],can[8]={0x14,7,0,9,0x1f,0,7,6}; + uint16_t out[124];size_t n;unsigned token,error; + init();select_mode(&a,2);rtu(req,16,6,0x1407,123); + assert(set_regmap_request(&a.map,6,0x1407,123,out,1,&n)==3&&n==0); + assert(set_regmap_request(&a.map,3,0x1400,0,out,124,&n)==3); + assert(set_regmap_request(&a.map,3,0x1400,125,out,124,&n)==3); + assert(set_regmap_request(&a.map,4,0x1400,1,out,124,&n)==1); + assert(!set_emu_server_rtu(&a,req,8,reply,8)); + assert(a.igbt.delay_ms==1); + assert(!set_emu_server_can(&a,can,8,out,2,&token,&error)); + assert(a.igbt.delay_ms==1); + req[7]^=1;assert(!set_emu_server_rtu(&a,req,8,reply,256));req[7]^=1; + req[0]=17;seal(req,6);assert(!set_emu_server_rtu(&a,req,8,reply,256)); + rtu(req,16,6,0x1407,123); + assert(set_emu_server_rtu(&a,req,8,reply,256)==8&&!memcmp(reply,req,8)); + assert(read1(&a,0x1407)==123&&b.igbt.delay_ms==1); + rtu(req,16,6,0x1407,1001); + assert(set_emu_server_rtu(&a,req,8,reply,256)==5); + assert(reply[1]==0x86&&reply[2]==3&&!set_crc16_modbus(reply,5)); + assert(a.igbt.delay_ms==123); + assert(set_emu_server_can(&a,can,8,out,124,&token,&error)==2); + assert(token==7&&!error&&a.igbt.delay_ms==9&&out[0]==0x1407&&out[1]==9); + can[7]=3;can[2]=0;can[3]=1; + assert(set_emu_server_can(&a,can,8,out,124,&token,&error)==2&&out[0]==9&&out[1]==0); + assert(set_regmap_can_fragment(out,2,0,token,error,reply,8)==8); + assert(!memcmp(reply,(const uint8_t[]){0,9,0,0,0x1f,0,7,0},8)); + assert(!set_regmap_can_fragment(out,2,1,token,error,reply,8)); + can[4]=0;assert(!set_emu_server_can(&a,can,8,out,124,&token,&error)); + assert(token==0&&error==0); +} +static void independent_wave_and_igbt(void) +{ + uint8_t r[8],reply[256],can[8]={0xa0,0,0,42,0x1f,0,9,6}; + uint16_t w[124];unsigned token,error; + init(); + /* Identical addresses on different transports now select the same module. */ + rtu(r,16,3,SET_WAVE_BASE,8); + assert(set_emu_server_rtu(&a,r,8,reply,256)==21); + assert(reply[3]==0x57&&reply[4]==0x47); + rtu(r,16,3,SET_EMU_IGBT_BASE,16); + assert(set_emu_server_rtu(&a,r,8,reply,256)==37); + assert(reply[3]==0x49&&reply[4]==0x47); + select_mode(&a,3); + write(&a,0x1304,2); + assert(set_emu_server_can(&a,can,8,w,124,&token,&error)==2&&!error); + write(&a,0xa001,4095);write(&a,0x1306,0xa55a); + rtu(r,16,6,0x1302,1);assert(set_emu_server_rtu(&a,r,8,reply,256)==8); + assert(starts[0]==1&&starts[1]==0&&wave[0].running&&!wave[1].running); + assert(read1(&a,0xa000)==42); + rtu(r,16,6,0xa000,1);assert(set_emu_server_rtu(&a,r,8,reply,256)==5&&reply[2]==6); + rtu(r,16,6,0x1402,1); + assert(set_emu_server_rtu(&a,r,8,reply,256)==5&&reply[2]==6); + assert(wave[0].running&&!a.igbt.enabled); + select_mode(&a,2); + assert(stops[0]==2&&!wave[0].running&&!wave[0].ready&&!a.igbt.enabled); + write(&a,0x1407,500);assert(a.igbt.delay_ms==500); + write(&a,0x1402,1);write(&a,0x1411,2); + assert(a.igbt.rx==1); + select_mode(&a,3); + assert(!a.igbt.rx&&!a.igbt.enabled&&!wave[0].running); + rtu(r,16,6,0x1302,1); + assert(set_emu_server_rtu(&a,r,8,reply,256)==5&&reply[2]==3); /* needs fresh upload */ +} +static void igbt_timing(void) +{ + unsigned i; + init();select_mode(&a,2);write(&a,0x1402,1); + write(&a,0x1407,300);write(&a,0x1408,3); + set_igbt_tick(&a.igbt,255);assert(!a.igbt.rx&&a.igbt.event_count==8); + for(i=0;i<299;++i)set_igbt_tick(&a.igbt,255); + assert(!a.igbt.rx); + set_igbt_tick(&a.igbt,255);assert(a.igbt.rx==255); + set_igbt_tick(&a.igbt,255);set_igbt_tick(&a.igbt,255);assert(a.igbt.rx==255); + set_igbt_tick(&a.igbt,255);assert(!a.igbt.rx); + write(&a,0x1407,1);write(&a,0x1406,5); + set_igbt_tick(&a.igbt,0);set_igbt_tick(&a.igbt,0);assert(a.igbt.rx==255); + write(&a,0x1406,0);assert(a.igbt.rx==250); /* Preserve normal ACKs. */ + set_igbt_tick(&a.igbt,0);set_igbt_tick(&a.igbt,0);set_igbt_tick(&a.igbt,0); + assert(!a.igbt.rx); + write(&a,0x1403,128);write(&a,0x140c,1); + set_igbt_tick(&a.igbt,0);set_igbt_tick(&a.igbt,0);assert(a.igbt.rx==128); + assert(read1(&a,0x1403)==128); + write(&a,0x1402,0);assert(!a.igbt.rx); + for(i=0;i<1000;++i)set_igbt_tick(&a.igbt,255); + assert(!a.igbt.rx&&!b.igbt.rx); +} +static void tms_shared_state(void) +{ + unsigned i;uint8_t req[18]={1,65},reply[256];uint16_t words[124];size_t n; + init(); + assert(set_regmap_request(&a.map,6,0x1512,1,words,124,&n)==6); + write(&a,0x1219,1);write(&a,0x1218,0xa55a); + for(i=0;i<500;++i)set_emu_server_tick(&a,0); + write(&a,0x1508,5000);write(&a,0x150c,0x41);write(&a,0x150e,4);write(&a,0x1512,1); + for(i=0;i<1000;++i)set_emu_server_tick(&a,0); + assert(read1(&a,0x152e)==1000&&b.tms.frequency==0); + req[10]=0x41;seal(req,16); /* Leave target latched, no Setspeed. */ + assert(set_emu_server_rtu(&a,req,18,reply,256)==101); + assert(reply[30]==0xe8&&reply[31]==3&&!set_crc16_modbus(reply,100)); + req[10]=0x40;seal(req,16); + assert(set_emu_server_rtu(&a,req,18,reply,256)==101&&!read1(&a,0x1502)); + assert(!set_regmap_request(&a.map,3,0x1400,16,words,124,&n)); + /* Both register and CAN writes reject inactive executors. */ + assert(set_regmap_request(&a.map,6,0x1402,1,words,124,&n)==6); + assert(set_regmap_request(&a.map,6,0x1302,1,words,124,&n)==6); + set_emu_server_tick(&a,255);assert(!a.igbt.rx); + select_mode(&a,2);assert(!a.tms.frequency&&!a.igbt.enabled); +} +static void stream_and_custom_composition(void) +{ + set_emu_stream stream={0};uint8_t req[8],reply[256];size_t n=0;unsigned i; + set_emu_server no_wave;uint16_t out[124];size_t count; + init();select_mode(&a,2);rtu(req,16,6,0x1407,19); + for(i=0;i<8;++i)n=set_emu_stream_feed(&stream,&a,req[i],i*20,reply,256); + assert(n==8&&a.igbt.delay_ms==19&&b.igbt.delay_ms==1); + assert(!set_emu_server_init(&no_wave,banks[2],banks[3],32,0,16,1,4,5,1)); + assert(no_wave.map.count==5); + assert(set_regmap_request(&no_wave.map,3,0x1300,8,out,124,&count)==2); + assert(!set_regmap_request(&no_wave.map,3,0x1400,16,out,124,&count)); + assert(set_emu_server_init(&no_wave,banks[2],banks[3],820,0,16,1,4,5,1)==3); +} +static void all_mode_pairs(void) +{ + unsigned from,to,i,error,token;uint16_t out[124];size_t n; + uint8_t can[8]={0x14,2,0,1,0x1f,0,7,6}; + for(from=0;from<4;++from)for(to=0;to<4;++to) { + init();select_mode(&a,from); + if(from==3) { + write(&a,0x1304,2);write(&a,0xa000,1);write(&a,0xa001,4095); + write(&a,0x1306,0xa55a);write(&a,0x1302,1); + } else if(from==2) { + write(&a,0x1402,1);write(&a,0x1411,2);assert(a.igbt.rx==1); + } else if(from==0)write(&a,0x1000,1); + write(&a,0x1219,(uint16_t)to);write(&a,0x1218,0xa55a); + for(i=0;i<499;++i)set_emu_server_tick(&a,255); + assert(a.config[3]==from); + set_emu_server_tick(&a,255);assert(a.config[3]==to); + if(from!=to) { + assert(!wave[0].running&&!wave[0].ready&&!a.igbt.rx&&!a.igbt.enabled); + assert(!a.tms.frequency&&a.ump.state==3); + } else if(from==3)assert(wave[0].running); /* a read/apply must not restart output */ + if(to!=3)assert(set_regmap_request(&a.map,6,0x1302,1,out,124,&n)==6); + if(to!=1)assert(set_regmap_request(&a.map,6,0x1512,1,out,124,&n)==6); + if(to!=2) { + (void)set_emu_server_can(&a,can,8,out,124,&token,&error); + assert(error==6&&!a.igbt.enabled); + } + } +} +int main(void) +{ + all_mode_pairs();directory_and_relocation();capacity_and_frames();independent_wave_and_igbt(); + igbt_timing();tms_shared_state();stream_and_custom_composition(); + puts("emulator services: directory, bounds, relocation, instances, RTU/CAN/stream, wave/TMS/IGBT OK"); + return 0; +} diff --git a/c/set-protocol/tools/build_host.py b/c/set-protocol/tools/build_host.py index 428a488..98e3e24 100644 --- a/c/set-protocol/tools/build_host.py +++ b/c/set-protocol/tools/build_host.py @@ -20,6 +20,7 @@ INCLUDE = ROOT / "include" JNI_INCLUDES: list[Path] = [] SOURCES = [ ROOT / "src" / name for name in ( + "set_regmap.c", "set_emu_ump.c", "set_emu_tms.c", "set_emu_igbt.c", "set_emu_server.c", "set_wavegen.c", "altera_logic.c", "altera_stream.c", "set_protocol.c", "set_can.c", "set_firmware.c", "set_telemetry.c", "set_plot.c", "set_trends.c", "set_spectrum.c",