44 Commits

Author SHA1 Message Date
79411dbc26 Add shared emulator services, waveform generation and STM32 USB boot ports 2026-09-28 11:54:17 +03:00
5260186c16 Добавить протокол Altera Logic и общие клиенты прошивки 2026-09-19 07:12:09 +03:00
10bfa587c7 perf(ds18b20-ds2480): передавай байты в Data Mode 2026-09-15 22:45:07 +03:00
ac3025ee32 feat(ds18b20-ds2480): добавь порт F407 и асинхронную подтяжку 2026-09-15 22:32:30 +03:00
37508f058b merge: сохрани историю NAND, уже включённую в c/parallel-nand 2026-09-15 22:15:19 +03:00
3334f4cddd Merge remote-tracking branch 'origin/feature/konor-runtime-settings' 2026-09-15 22:15:07 +03:00
aedd5fc082 merge: объедини DS2480 и актуальный templates в main 2026-09-15 22:15:06 +03:00
9ba3742b29 feat(ds18b20-ds2480): добавь драйвер датчиков через UART-мост 2026-09-15 16:57:33 +03:00
4f23f48896 Объединить разработки CAN и STM32 в master 2026-09-15 15:57:48 +03:00
8e9fd144ca Сохранить текущие изменения NAND и LED перед объединением веток 2026-09-15 15:57:06 +03:00
49c65ea78d fix(set-protocol): сохрани совместимость порта с ARMCC 2026-09-07 08:42:02 +03:00
34f728cfaa fix(set-protocol): убери libc из порта DevBoard F407 2026-09-07 08:41:26 +03:00
72205216f5 feat(set-protocol): добавь порт F407 для DevBoard_V1 2026-09-07 08:32:02 +03:00
5d48ddf0b0 Добавить HTML-справочник CAN v1 и v2 2026-09-06 03:09:53 +03:00
245d486a63 Добавить HTML-справочник CAN v1 и v2 2026-09-06 03:03:59 +03:00
e262b0acdd Добавить порт SETProtocol v2 для TMS320F2812 2026-09-06 02:14:17 +03:00
ed92ec346e Расширить общий протокол CAN_Bal_2812 2026-09-06 01:06:30 +03:00
3d374e7777 Добавить общую модель конструктора ProtoCAN для Android 2026-09-06 00:11:25 +03:00
d03e039cb4 Merge remote-tracking branch 'origin/trend-display-scale' into HEAD 2026-09-05 11:43:08 +03:00
d002b14e4e Share PM67 upload protocol through C core 2026-09-05 11:37:03 +03:00
11007b0704 Добавить множитель и IQ в Python-тренды 2026-09-05 03:45:05 +03:00
821202b655 Добавить выпуск прошивок из Keil и CCS 2026-09-05 03:16:16 +03:00
8596918bf7 Добавить публикацию каталога прошивок 2026-09-05 03:05:17 +03:00
bada3bbe8d Сохранить совместимость ABI графиков 2026-09-05 02:44:08 +03:00
87f2f2f56b Дополнить общий контракт границ осей 2026-09-05 02:43:07 +03:00
5523f049ec Расширить общие API графиков и GAS обмена 2026-09-05 02:37:11 +03:00
b08ea02a5c Align Android parser statistics with C core 2026-09-04 21:10:02 +03:00
1b3a6318eb Move PM35 protocol into shared C core 2026-09-04 21:06:40 +03:00
57622ccd66 Добавить Android API протокола ПМ35 2026-09-04 20:39:04 +03:00
ba529f579c Merge pull request 'Расширить графики и общие протоколы CAN и Periph28335' (#4) from feature/plot-can-periph-updates into master
Reviewed-on: #4
2026-09-04 19:18:54 +03:00
4f788d39d6 Убрать лишнюю строку в Legacy CAN API 2026-09-04 19:17:27 +03:00
1799b00167 Добавить канал TMS в каталог прошивок 2026-09-04 19:15:48 +03:00
940d9ada76 Добавить общий протокол Periph28335 2026-09-04 19:15:48 +03:00
6eaa47d3a8 Добавить общий Python API старого CAN terminal 2026-09-04 19:15:47 +03:00
1f49ba49bb Добавить общий API старого CAN terminal 2026-09-04 19:15:47 +03:00
b7f00acb24 feat(plot): lock axes and calculate marker levels in dB 2026-09-04 19:15:47 +03:00
70b2ee3cad feat(plot): configure one or two marker pairs per axis 2026-09-04 19:15:47 +03:00
45582cda4a Merge pull request 'Добавить общие графики и драйвер parallel NAND' (#3) from feature/shared-plot-and-parallel-nand into master
Reviewed-on: #3
2026-09-04 12:39:12 +03:00
2bb9fea90a Убрать лишнюю строку в модели маркеров 2026-09-04 12:36:56 +03:00
6392d36741 Добавить общий Python-парсер каталога прошивок 2026-09-04 12:31:42 +03:00
0c635ce1be Добавить безопасный клиент каталога прошивок Android 2026-09-04 12:30:37 +03:00
5caca07ad4 Поддержать настройки трендов в Python 3.8 2026-09-04 12:23:07 +03:00
31a111ee46 Добавить переносимый драйвер parallel NAND 2026-09-04 12:22:21 +03:00
20f63bc389 Добавить общие графики, декодер KONOR и порт STM32 bxCAN 2026-09-04 12:22:17 +03:00
92 changed files with 6898 additions and 10 deletions

View File

@@ -50,6 +50,9 @@ templates/
| Модуль | Что делает | Зависимости |
|---|---|---|
| [`python/setprotocol/firmware_database.py`](python/setprotocol/firmware_database.py) | база прошивок: HTTPS-каталог, скачивание с SHA-256 и кэшем, публикация в Gitea без SETGUI; [подключение и CLI](tools/firmware-publish/DATABASE.md) | stdlib, Python 3.10+ |
| [`python/setprotocol/firmware_publish.py`](python/setprotocol/firmware_publish.py) | общие метаданные публикации, SHA-256, release tag и обновление каталога прошивок; [подключение](tools/firmware-publish/PORTING.md) | stdlib; сетевой адаптер в SETGUI |
| [`python/setprotocol/firmware_catalog.py`](python/setprotocol/firmware_catalog.py) | модель и parser каталога `firmware.releases` | stdlib |
| [`python/protocan`](python/protocan) | разбор ProtoCAN, транспортный кадр моста, кадр SETGUI, кодеки каталога | stdlib, Python 3.9+ |
| [`python/protocan/trends.py`](python/protocan/trends.py) | общие настройки графиков, ограниченная история, ctypes-декодер GAS/raw CAN | stdlib, опционально SETProtocol DLL/SO |

View File

@@ -1,11 +1,144 @@
# PORTING
# Портирование firmware-info
Аппаратно-зависимый контракт ограничен `firmware_info_config.h`:
`FIRMWARE_VERSION_MAJOR`, `FIRMWARE_VERSION_MINOR`, `FIRMWARE_VERSION_PATCH` и
необязательный `FIRMWARE_BUILD_ID`. Выберите порт по семейству МК или создайте
его копию. Публикация — ответственность транспорта: 12 `uint16_t` для Modbus
либо 24 little-endian байта для SETGUI.
## Назначение и границы
При переносе проверьте: поддержку `__DATE__`/`__TIME__`, наличие generated в
include path, запуск генератора до компиляции и декодирование build ID как двух
ASCII-байтов в каждом логическом слове.
Это C-библиотека описания работающего образа: SemVer, дата/время компиляции,
8 символов build ID и сериализация контракта v1. Она не обращается к сети,
не читает версию из сервера и не записывает Flash. HAL, RTOS, UART и CAN
ядру не нужны. Отправку результата выполняет приложение.
Для размещения `.hex/.bin` в каталоге SETGUI уже существуют
[`setprotocol.firmware_publish`](../../python/setprotocol/firmware_publish.py)
и [`tools/firmware-publish`](../../tools/firmware-publish/README.md).
Связь компонентов и перенос публикации описаны в
[`tools/firmware-publish/PORTING.md`](../../tools/firmware-publish/PORTING.md).
## 1. Файлы и конфигурация
Пример ниже предполагает `lib/templates` внутри нового проекта:
```text
project/
inc/firmware_info_config.h
generated/firmware_build_id.h # создаётся до сборки
lib/templates/c/firmware-info/
src/
```
Добавьте в сборку оба файла:
```text
lib/templates/c/firmware-info/src/firmware_info.c
lib/templates/c/firmware-info/src/firmware_info_port.c
```
В include path добавьте `inc`, `generated` и
`lib/templates/c/firmware-info/include`. Встроенный `CMakeLists.txt` собирает
только ядро и host-тест; `firmware_info_port.c` и каталоги конфигурации нужно
добавлять к целевому firmware target самостоятельно.
Скопируйте `ports/<mcu>/firmware_info_config.template.h` как
`inc/firmware_info_config.h`. Есть варианты STM32F1/F4/G4 и К1921ВК028;
они не содержат регистров МК. Для другого МК с обычными 8-битными байтами
достаточно такого же config:
```c
#ifndef FIRMWARE_INFO_CONFIG_H
#define FIRMWARE_INFO_CONFIG_H
#define FIRMWARE_VERSION_MAJOR 1U
#define FIRMWARE_VERSION_MINOR 2U
#define FIRMWARE_VERSION_PATCH 3U
#include "firmware_build_id.h"
#endif
```
Обязательный include в этом примере позволяет обнаружить пропущенный pre-build.
В готовых шаблонах include опциональный через `__has_include`; если заголовок
не подключён, порт использует `LOCALDEV`. Для компилятора без `__has_include`
используйте явный include. Можно связать три версии с существующими макросами
проекта, как это сделано в `KONOR_ds18b20/inc/firmware_info_config.h`.
## 2. Build ID
Из корня нового проекта перед компиляцией запустите:
```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File "lib/templates/c/firmware-info/tools/make_build_id.ps1" -Repository "." -Output "generated/firmware_build_id.h"
```
Указывайте `-Repository` и `-Output` явно: расположение сабмодуля и рабочий
каталог IDE различаются между проектами. `-Repository` должен указывать на
исходники прошивки, а не на репозиторий `templates`. Для Keil из каталога `mdk`
соответственно используйте `..\lib\templates\...`, `-Repository ".."` и
`-Output ".\Generated\firmware_build_id.h"`.
При доступном Git чистый checkout получает 8 знаков commit, изменения tracked
файлов — 7 знаков и `+`. Если commit определить нельзя, используется `NOGIT000`.
Новые untracked-файлы текущий генератор при определении dirty не учитывает.
Дата/время берутся из `__DATE__`/`__TIME__` при компиляции `firmware_info_port.c`;
для выпуска выполняйте полный Rebuild, чтобы не оставить старый объектный файл.
## 3. Обработчик запроса версии
Пример функции подготовки полезной нагрузки, вызываемой вашим обработчиком:
```c
#include "firmware_info_port.h"
firmware_info_status_t app_make_firmware_info(uint8_t *payload, size_t capacity)
{
firmware_info_t info;
firmware_info_status_t status = firmware_info_port_describe(&info);
if (status != FIRMWARE_INFO_OK) return status;
return firmware_info_to_le_bytes(&info, payload, capacity);
}
```
При успехе передайте ровно `FIRMWARE_INFO_PAYLOAD_SIZE` (24) байта в собственный
транспорт. При ошибке верните ошибку протокола, а не содержимое буфера.
Не отправляйте `sizeof(firmware_info_t)`: структура не является wire format.
В KONOR обработчик `app_send_firmware_info()` отвечает на
`PROTO_MSG_FIRMWARE_INFO = 0x03`, сохраняя sequence запроса. Старый 32-байтовый
`DEVICE_INFO` остаётся отдельным сообщением. Для нового протокола сначала
согласуйте команду с клиентом: подключение библиотеки само по себе не добавит
декодер и отображение версии в GUI.
Для Modbus вызовите `firmware_info_to_registers()` с массивом из 12 `uint16_t`
и разместите его в выбранной карте регистров. Адреса и Modbus-порядок байтов
обеспечивает ваш Modbus-стек; LE-буфер в Modbus напрямую не копируйте.
## 4. Контракт v1
| Индекс слова | Содержимое |
|---|---|
| 0 | Версия контракта: 1 |
| 1, 2, 3 | major, minor, patch |
| 4 | Год |
| 5 | `(month << 8) \| day` |
| 6 | `(hour << 8) \| minute` |
| 7 | Секунды |
| 8–11 | По два ASCII-символа build ID: первый в старшем байте слова |
В LE-представлении младший байт каждого слова идёт первым. Например, build ID
`ab` в начале строки даёт слово `0x6162`, но байты `62 61`; декодируйте сначала
слово, затем символы из старшего/младшего байта. NUL-терминатор не передаётся.
Текущая проверка допускает major/minor до 255 и patch до 999. Если в каталоге
используется `(major << 16) | (minor << 8) | patch`, ограничьте **все три** части
до 255, иначе значения пересекутся. Версия каталога автоматически из C-config
не извлекается.
Для C2000 с 16-битным `char` нельзя считать готовым байтовый порт: отдельно
проверьте наличие `uint8_t` и представление октетов в транспорте. Публикация
TMS SCI8-файла с ПК поддерживается независимо от переноса этой C-библиотеки.
## 5. Проверка переноса
1. Соберите host-тест из `tests/test_firmware_info.c` вместе с ядром либо
используйте CMake/CTest. Он проверяет дату, регистры и порядок байтов build ID.
2. Соберите целевую прошивку с обоими `.c` и сгенерированным заголовком.
3. Запросите версию у устройства: сравните SemVer, build ID, время и 24-байтовый
ответ с конкретной сборкой. Проверьте, что прежний `DEVICE_INFO` не изменился.
4. Подключите выпуск по инструкции публикатора; сравните версию в config и
`firmware-release.cmd` перед Rebuild и `--preflight`.

View File

@@ -25,3 +25,7 @@ config, а не дублирования ядра.
KONOR публикует эти 24 байта ответом `FIRMWARE_INFO (0x03)`, сохраняя старый
32-байтовый `DEVICE_INFO` без изменений.
Подробная инструкция с кодом обработчика, форматом ответа и проверкой переноса:
[`PORTING.md`](PORTING.md). Для размещения файла прошивки в каталоге SETGUI
используется отдельный [общий публикатор](../../tools/firmware-publish/PORTING.md).

View File

@@ -0,0 +1,93 @@
# Altera Logic: общее ядро и порты
Потоковый SETCAN/GAS-профиль и общие порты описаны отдельно:
[Altera Logic ONLINE](../../doc/setcan/ALTERA_LOGIC_STREAM.md).
Ниже — совместимый прежний буферный протокол UART `A5 CMD`.
Клиент UART-протокола FPGA-анализатора Cyclone IV: 16 каналов, 4096 выборок.
Работает с существующим RTL `altera_loganalisator`, версия протокола 1.
Ядро C99 не зависит от Qt, ОС, системных часов и динамической памяти.
Слои: SETGUI → Python ctypes → C99 → Python Qt-порт → COM / USB–UART.
GUI передаёт числовые настройки и получает готовый массив; формат пакетов,
XOR, endian, последовательность команд, парсер и состояния тайм-аутов находятся в C.
| Файл | Назначение и зависимости |
|---|---|
| `include/altera_logic.h` | Публичный ABI, макрос экспорта из `pcan_abi.h` |
| `src/altera_logic.c` | Кодек и автомат обмена; только стандартная библиотека C |
| `tests/test_altera_logic.c` | Эталонные пакеты RTL, полный захват, ошибки |
| `../../python/altera_logic/native.py` | ctypes-модели, загрузка DLL, экспорт CSV; stdlib |
| `../../python/altera_logic/qt_port.py` | Асинхронный COM-порт; PySide6 QtSerialPort |
## Сборка
Из корня `templates`:
```powershell
python c/set-protocol/tools/build_host.py --output python/altera_logic/native/setprotocol.dll
```
На Linux выходной файл — `python/altera_logic/native/libsetprotocol.so`.
Также поддерживается основная CMake-сборка `c/set-protocol`; путь к результату
задаётся через `ALTERA_LOGIC_LIBRARY`. Библиотека без символов `la_*`
не поддерживается: тихого Python-fallback нет.
## Контракт порта
Выделить выровненную память размером `la_context_size()`; все обращения к
одному контексту выполняются в одном потоке.
- `la_init(ctx)` — начать с INFO.
- `la_next(ctx, out, capacity)` — получить следующий запрос, либо 0, если отправлять нечего.
- `la_feed(ctx, bytes, size)` — передать принятые байты, включая частичные ответы.
- `la_tick(ctx, elapsed_ms)` — сообщить прошедшее время; тайм-аут ответа 1000 мс.
- `la_start(ctx, divider, mask, value, edge_mask, edge_value)` — настройка и новый захват.
- `la_get(ctx, field)` — состояние и прогресс; `la_samples` — завершённая запись.
- `la_fail(ctx, LA_IO_ERROR)` — сообщить ошибку транспорта.
COM: 921600 бод, 8N1, без управления потоком. Одновременно ожидается только
один ответ. После ARM STATUS опрашивается каждые 25 мс; ожидание самого
триггера не ограничено. После done читаются 64 блока по 64 выборки.
Ошибки закрывают сеанс без автоматического повтора ARM. Неверный размер,
код команды или XOR прекращает сеанс. При рассинхронизации запроса на FPGA
может потребоваться аппаратный RESET_N; переподключение не гарантирует её сброс.
Клиент намеренно принимает только профиль 16 × 4096 версии 1.
Поддержка внешней SDRAM и расширенных адресов сюда пока не входит.
Прерывание в GUI закрывает порт ПК: STOP в протоколе FPGA отсутствует.
## Быстрый старт без платы
Добавить `templates/python` в PYTHONPATH:
```python
from altera_logic import NativeAnalyzer
client = NativeAnalyzer()
client.reset(demo=True)
client.start(divider=49)
capture = client.capture()
print(len(capture.samples)) # 4096
print(capture.sample_rate) # 1_000_000
print(capture.trigger_index) # 2048
capture.save_csv("demo.csv")
```
Демо создаёт синтетическую запись в C; не моделирует заданные условия триггера.
CSV содержит явный признак demo, время относительно триггера и состояния D0…D15.
## Потребители и проверка
Используется вкладкой SETGUI «Altera Logic». Для Android/JNI новый потребитель
может использовать тот же ABI; интерфейс Android в этой задаче не изменяется.
Существующий ABI SETProtocol не изменён, добавлены только символы `la_*`.
```powershell
$env:PYTHONPATH = "$PWD/python"
python -m unittest discover -s python/tests -p test_altera_logic.py
```
CMake включает `test_altera_logic` и CTest `altera_logic`.
На плате требуется отдельно проверить разводку пинов, RAM-модуль и прошивку;
программные проверки не заменяют аппаратную проверку.

View File

@@ -6,9 +6,12 @@ set(CMAKE_C_STANDARD_REQUIRED ON)
# SET protocol v2: управление, телеметрия, CAN segmentation и firmware flow.
set(SETPROTOCOL_V2_SOURCES
src/altera_logic.c
src/altera_stream.c
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
@@ -34,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}
)
@@ -79,6 +88,20 @@ 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)
add_executable(test_altera_stream tests/test_altera_stream.c)
target_link_libraries(test_altera_stream PRIVATE setprotocol_static)
add_test(NAME altera_stream COMMAND test_altera_stream)
add_executable(test_plot tests/test_plot.c)
target_link_libraries(test_plot PRIVATE setprotocol_static)

View File

@@ -1,5 +1,8 @@
# SETProtocol
Универсальные сервисы эмуляторов: [адресное пространство, API и порт новой платы](docs/EMULATOR_SERVICES.md).
`set_regmap` объединяет УМП, 2812, IGBT и DAC за общим API для RTU, CAN и других оболочек.
Единое переносимое протокольное ядро SET для `SETGUI`, Android GUI, устройств
и сервисных утилит. В одном C99-проекте собраны:

View File

@@ -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 выключен, ЦАП не запущен.

View File

@@ -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 эмулятора.
Аппаратная проверка амплитуды, периода и формы осциллографом не выполнена.

View File

@@ -0,0 +1,35 @@
/** Altera logic analyzer UART client. C99, caller-owned memory, no OS/heap.
* One request at a time; no automatic retries (ARM is not idempotent).
* Initialize aligned storage of la_context_size() bytes, then call la_next,
* la_feed and la_tick from one thread. tick receives elapsed milliseconds.
*/
#ifndef ALTERA_LOGIC_H
#define ALTERA_LOGIC_H
#include "pcan_abi.h"
#ifdef __cplusplus
extern "C" {
#endif
enum { LA_CONNECTING, LA_READY, LA_CAPTURING, LA_DONE, LA_ERROR };
enum { LA_OK, LA_BAD_FRAME, LA_CHECKSUM, LA_DEVICE_ERROR, LA_TIMEOUT,
LA_UNSUPPORTED, LA_ARGUMENT, LA_IO_ERROR };
enum { LA_STATE, LA_ERROR_CODE, LA_CHANNELS, LA_DEPTH, LA_CLOCK_HZ,
LA_FLAGS, LA_TRIGGER_INDEX, LA_READ_COUNT, LA_DIVIDER };
PCAN_ABI_API size_t la_context_size(void);
PCAN_ABI_API void la_init(void *ctx);
/** Return 0 on success. Configuration is immutable throughout this capture. */
PCAN_ABI_API int la_start(void *ctx, uint32_t divider, uint32_t mask,
uint32_t value, uint32_t edge_mask, uint32_t edge_value);
/** Returns 6 when a request is ready, otherwise 0. Capacity must be >=6. */
PCAN_ABI_API size_t la_next(void *ctx, uint8_t *out, size_t capacity);
PCAN_ABI_API void la_feed(void *ctx, const uint8_t *data, size_t size);
PCAN_ABI_API void la_tick(void *ctx, uint32_t elapsed_ms);
PCAN_ABI_API void la_fail(void *ctx, uint32_t code);
PCAN_ABI_API uint32_t la_get(const void *ctx, uint32_t field);
PCAN_ABI_API size_t la_samples(const void *ctx, uint16_t *out, size_t capacity);
/** Offline fixture, never communicates with hardware or claims a real trigger. */
PCAN_ABI_API void la_demo_init(void *ctx);
PCAN_ABI_API int la_demo_capture(void *ctx, uint32_t divider);
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -0,0 +1,46 @@
/** SETCAN/GAS Altera Logic online stream, shared by CAN and UART.
* Caller-owned context; one serialized caller. No heap, OS or Qt dependencies.
*/
#ifndef ALTERA_STREAM_H
#define ALTERA_STREAM_H
#include "pcan_abi.h"
#include "pcan_frame.h"
#ifdef __cplusplus
extern "C" {
#endif
#define LAS_DEVICE_TYPE 6U
#define LAS_DEVICE_ID 14U
#define LAS_GAS_META 0xFE00U
#define LAS_GAS_DATA 0xFF00U
#define LAS_CAPACITY 8192U
#define LAS_TAG 0x4C01U
enum { LAS_COUNT, LAS_PERIOD_NS, LAS_SESSION, LAS_RECEIVED, LAS_MISSING,
LAS_DUPLICATES, LAS_INVALID, LAS_IGNORED, LAS_CRC_ERRORS, LAS_HAS_META };
PCAN_ABI_API size_t las_context_size(void);
PCAN_ABI_API int las_init(void *ctx, uint32_t device_id);
PCAN_ABI_API void las_can(void *ctx, uint32_t id, const uint8_t *data, size_t size,
uint32_t extended, uint32_t remote);
PCAN_ABI_API void las_uart(void *ctx, const uint8_t *data, size_t size);
PCAN_ABI_API uint32_t las_get(const void *ctx, uint32_t field);
/** Chronological bounded history. breaks[i]=1 marks a discontinuity before i. */
PCAN_ABI_API size_t las_snapshot(const void *ctx, uint64_t *indices, uint16_t *samples,
uint8_t *breaks, size_t capacity);
/** Sparse step trace for rendering; capacity >= 2*LAS_COUNT, move=pen-up. */
PCAN_ABI_API size_t las_trace(const void *ctx, uint32_t channel, uint64_t *indices,
uint8_t *levels, uint8_t *moves, size_t capacity);
PCAN_ABI_API uint32_t las_device_type(void);
PCAN_ABI_API uint32_t las_device_id(void);
PCAN_ABI_API const char *las_device_name(void);
PCAN_ABI_API void las_demo_step(void *ctx, uint32_t count);
/** Device-side packet builders; return zero for invalid arguments.
* uart=0: 8-byte CAN data + *id; uart=1: complete AA55 transport + *id.
* Same builders are used by firmware ports, tests and the demo producer.
*/
PCAN_ABI_API size_t las_metadata(uint32_t device, uint32_t session, uint32_t period_ns,
uint32_t uart, uint32_t *id, uint8_t *out, size_t capacity);
PCAN_ABI_API size_t las_data(uint32_t device, uint32_t session, uint32_t index,
uint32_t sample, uint32_t uart, uint32_t *id, uint8_t *out, size_t capacity);
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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"

View File

@@ -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 <stdint.h>
#include <stddef.h>
#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

View File

@@ -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=&regions[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);
}

View File

@@ -0,0 +1,54 @@
/* Shared SET USB boot dispatcher. The MCU wrapper supplies BOOT_* macros. */
#include "usb_setp_stream.h"
#include <string.h>
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;
}

View File

@@ -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).

View File

@@ -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<UMP_RTU_CAPACITY) return 0;
for (i=0;i<8;i++) r[i]=request[i];
n=ump_rtu(b,device,r,8,out,UMP_RTU_CAPACITY);
for (i=0;i<n;i++) reply[i]=(uint8_t)out[i];
return n;
}
unsigned ump_stm32f407_can(const ump_backend *b, const uint8_t *request,
size_t length, uint16_t *words, unsigned *token, unsigned *error)
{
ump_octet r[8];
unsigned i;
if (!request || length!=8) return 0;
for (i=0;i<8;i++) r[i]=request[i];
return ump_can_request(b,r,8,words,token,error);
}
unsigned ump_stm32f407_fragment(const uint16_t *words, unsigned count,
unsigned index, unsigned token, unsigned error, uint8_t reply[8])
{
ump_octet out[8];
unsigned i;
if (!reply || !ump_can_fragment(words,count,index,token,error,out)) return 0;
for (i=0;i<8;i++) reply[i]=(uint8_t)out[i];
return 8;
}

View File

@@ -0,0 +1,11 @@
#ifndef UMP_STM32F407_H
#define UMP_STM32F407_H
#include "../../include/ump_protocol.h"
/* Application owns framing, ID filtering, snapshot lock, and tx buffers. */
size_t ump_stm32f407_rtu(const ump_backend *backend, unsigned device,
const uint8_t *request, size_t length, uint8_t *reply, size_t capacity);
unsigned ump_stm32f407_can(const ump_backend *backend, const uint8_t *request,
size_t length, uint16_t *words, unsigned *token, unsigned *error);
unsigned ump_stm32f407_fragment(const uint16_t *words, unsigned count,
unsigned index, unsigned token, unsigned error, uint8_t reply[8]);
#endif

View File

@@ -0,0 +1,68 @@
# STM32F407VET6 USB boot port
SET v2 single-slot updater: `include/set_boot.h`, `src/set_boot.c` and the existing
`set_firmware` codec. Byte transport is supplied by the application, independently
of the Flash callbacks. The emulator project supplies the ST USB CDC stream.
## Integration in another F407 project
1. Copy `boot_config.template.h` to the project's include directory as
`boot_config.h`: set crystal frequency, SET node and product model. Match
the crystal frequency with HAL `HSE_VALUE`.
2. Compile `src/set_protocol.c`, `src/set_firmware.c`, `src/set_boot.c` and this
port's `boot_main.c`, `boot_flash.c`, `boot_request.c`, `boot_usb_stream.c`.
3. Provide STM32F4 CMSIS/HAL, startup, the ST USB device CDC middleware and the
board USB transport (`usb_cdc.h`, `usb_setp_stream.h` and their implementation).
The transport calls `usb_setp_feed`/`usb_setp_reset` from this port instead of
an application command dispatcher. Enable SET v2 for the bootloader build.
4. Link boot code below `0x0800C000`; link the application at `0x08010000`.
Add `boot_request.c` to the application and periodically call
`f407_reset_poll`; its update command calls `f407_request_boot`.
The emulator's `Bootloader/Src/*.c` files are include-only build wrappers. Neither
the portable updater nor this port depends on the emulator models or its
`board_config.h`. The USB transport and HAL configuration remain board supplied.
Flash layout (512 KiB device): boot sectors 0..2 at `0x08000000` (48 KiB),
commit record in sector 3 at `0x0800C000`, application sectors 4..7 at
`0x08010000` (448 KiB). All addresses and sector operations are in this port.
The sector programming/jump approach is adapted from climate's
`climate_control_f407vet6_f4/Bootloader/bootloader_f407.c`.
BEGIN requires slot 0, base `0x08010000`, size 8..458752, block size 1..256,
ERASE_SLOT flag only, zero SHA/key/signature. DATA must be sequential; its CRC,
length and bounds are checked. END requires complete receipt and matching size
and CRC. The port verifies the actual Flash CRC and vector table before writing
the commit marker last. SHA, signatures, automatic activation and resume are
not supported and are rejected explicitly. CRC is an integrity check.
STATUS returns the standard 16-byte SET firmware status. ACTIVATE accepts an
empty payload after END; the transport adapter defers reset to let the USB
reply finish. ABORT clears the RAM session; it does not restore erased firmware.
On reset, SP and reset PC are checked like in climate. USB metadata is not
required, so Keil/SWD installs and alternating SWD/USB updates work even with
absent or stale USB metadata. Preserve the bootloader using sector erase rather
than full-chip erase. Reflash the updated bootloader once to adopt this behavior.
USB END/ACTIVATE still require complete receipt and CRC verification.
Like climate, the reset vector check cannot detect incomplete images once valid
vectors are written: after an interrupted update and reset such an image may run.
To recover an unresponsive app, erase sector 4 at `0x08010000` via SWD, reset, and
upload the full image again. Erasing metadata sector 3 no longer forces bootloader
entry. An interrupted update requires a fresh BEGIN and full retransmission.
`test_boot_image.c` is a host-only regression test; do not add it to firmware targets.
`boot_request.c` is shared by the application and bootloader. Product command
`0x1003` (empty payload) in the application sets RTC backup register 0 and
schedules reset after 500 ms. The bootloader consumes that flag before deciding
whether to jump. Reserve BKP0R for this port. The jump must run early after reset,
before enabling peripheral interrupts, USB or DMA. Both images must set VTOR
to their own link address in SystemInit.
Board: PA11 D-/PA12 D+, existing self-powered OTG_FS CDC port, 25 MHz HSE;
no VBUS host drive, PA9/PA10 are not used. Flash erase uses x32 at nominal 3.3 V,
programming uses bytes to support unaligned protocol blocks. Hardware acceptance
must cover disconnect/power loss during erase, programming and commit.
The SET USB command dispatcher is shared with G474 in
`../stm32-usb-boot/boot_usb_protocol.inc`; keep that directory when importing this port.

View File

@@ -0,0 +1,9 @@
#ifndef F407_BOOT_CONFIG_H
#define F407_BOOT_CONFIG_H
/* Copy to the board include directory as boot_config.h.
* HSE must be an integer MHz frequency; PLL input is 1 MHz.
* The Flash layout in boot_port.h targets the 512 KiB STM32F407VE. */
#define F407_BOOT_HSE_HZ 25000000UL
#define F407_BOOT_NODE 16U
#define F407_BOOT_MODEL "STM32F407-USB-BOOT"
#endif

View File

@@ -0,0 +1,88 @@
/* Flash geometry and jump sequence derived from climate's F407 bootloader.
* Sector 3 is a durable commit record; sectors 4..7 hold the application.
* Reset checks vectors like climate; USB END always verifies Flash. */
#include "boot_port.h"
#include "boot_image.h"
#include "stm32f4xx_hal.h"
#include <string.h>
#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<<FLASH_CR_PSIZE_Pos) | (sector<<FLASH_CR_SNB_Pos);
FLASH->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);
}

View File

@@ -0,0 +1,19 @@
#ifndef F407_BOOT_IMAGE_H
#define F407_BOOT_IMAGE_H
#include "boot_port.h"
#include <string.h>
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

View File

@@ -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); }
}

View File

@@ -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

View File

@@ -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();
}

View File

@@ -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"

View File

@@ -0,0 +1,34 @@
#include "boot_image.h"
#include <assert.h>
#include <stdio.h>
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;
}

View File

@@ -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||ticks<psc) return 1;
arr=ticks/psc;
stop(context);
if(DMA1_Stream5->CR&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);
}

View File

@@ -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

View File

@@ -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.

View File

@@ -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<<i);
igbt_emu_set_tx(inputs); emu_tick(); outputs=igbt_emu_get_rx();
HAL_GPIO_WritePin(GPIOC,(uint16_t)(~outputs&0xffu),GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOC,outputs,GPIO_PIN_SET);
}

View File

@@ -0,0 +1,39 @@
/**
* Точка входа прошивки. Здесь только порядок запуска и совместное обслуживание модулей.
* Настройки оборудования находятся в board.c, обмен — в transport.c, генератор — в wave_hal.c.
* Все модели и разбор команд выполняются в главном цикле; ISR обслуживают HAL и передачу байтов.
*/
#include "board.h"
#include "transport.h"
#include "emulator.h"
#include "usb_cdc.h"
#include "wave_output.h"
#include "stm32g4xx_hal.h"
#if defined(EMU_USB_BOOT_ENABLE)
#include "boot_port.h"
#endif
int main(void)
{
uint32_t serviced;
/* HAL запускает миллисекундный SysTick до настройки PLL: его счётчик нужен таймаутам HAL. */
if (HAL_Init()!=HAL_OK) board_fatal(3);
/* Сначала создаём модели и значения по умолчанию, затем настраиваем плату с этими параметрами. */
emu_init(); board_init();
/* Начинаем модельное время после настройки платы, не отрабатывая задержки её инициализации. */
serviced=HAL_GetTick();
/* USB использует отдельный HSI48 с CRS: внешний кварц для USB не нужен. */
if (board_usb_ready()) usb_cdc_init();
for (;;) {
uint32_t now=HAL_GetTick();
/* Один пропущенный миллисекундный шаг за проход. Между шагами всегда обслуживаем связь,
* чтобы догоняющая обработка модели не вытесняла UART/CAN/USB. */
if (serviced!=now) { ++serviced; board_tick(); }
transport_uart_poll(now); transport_can_poll(now);
usb_cdc_poll(now); wave_output_poll();
#if defined(EMU_USB_BOOT_ENABLE)
g474_reset_poll(now);
#endif
}
}

View File

@@ -0,0 +1,163 @@
/**
* USART2/VCP и FDCAN1 в режиме Classic CAN. Формат запросов совпадает с F407:
* UART RTU, CAN extended ID 0xBA0000+mode-1; ответы 0xBA0020+mode-1.
* Здесь нет второго кодека: все байты обрабатывают emu_rtu/emu_can из templates.
*/
#include "transport.h"
#include "board.h"
#include "board_config.h"
#include "emulator.h"
#include "stm32g4xx_hal.h"
static UART_HandleTypeDef uart;
static FDCAN_HandleTypeDef can;
static volatile uint8_t transmitting;
static uint8_t can_ready;
volatile uint32_t uart_errors,uart_overflows,can_timeouts,can_overflows;
void transport_init(void)
{
FDCAN_FilterTypeDef filter={0};
uart.Instance=USART2; uart.Init.BaudRate=emu_uart_baud();
uart.Init.WordLength=UART_WORDLENGTH_8B; uart.Init.StopBits=UART_STOPBITS_1;
uart.Init.Parity=UART_PARITY_NONE; uart.Init.Mode=UART_MODE_TX_RX;
uart.Init.HwFlowCtl=UART_HWCONTROL_NONE; uart.Init.OverSampling=UART_OVERSAMPLING_16;
uart.Init.ClockPrescaler=UART_PRESCALER_DIV1;
if(HAL_UART_Init(&uart)!=HAL_OK) board_fatal(4);
HAL_NVIC_SetPriority(USART2_IRQn,6,0); HAL_NVIC_EnableIRQ(USART2_IRQn);
can.Instance=FDCAN1; can.Init.ClockDivider=FDCAN_CLOCK_DIV1;
can.Init.FrameFormat=FDCAN_FRAME_CLASSIC; can.Init.Mode=FDCAN_MODE_NORMAL;
can.Init.AutoRetransmission=ENABLE; can.Init.TransmitPause=DISABLE;
can.Init.ProtocolException=DISABLE;
/* PCLK1=80 МГц, 16 квантов/бит, prescaler=20 для 250 кбит/с.
* Sample point=(1+13)/16=87,5%; CAN FD/BRS на проводе не используется. */
if(HAL_RCC_GetPCLK1Freq()%(EMU_CAN_BITRATE*16u)) board_fatal(2);
can.Init.NominalPrescaler=HAL_RCC_GetPCLK1Freq()/(EMU_CAN_BITRATE*16u);
can.Init.NominalSyncJumpWidth=1; can.Init.NominalTimeSeg1=13; can.Init.NominalTimeSeg2=2;
can.Init.DataPrescaler=1; can.Init.DataSyncJumpWidth=1;
can.Init.DataTimeSeg1=1; can.Init.DataTimeSeg2=1;
can.Init.StdFiltersNbr=0; can.Init.ExtFiltersNbr=1;
can.Init.TxFifoQueueMode=FDCAN_TX_FIFO_OPERATION;
if(HAL_FDCAN_Init(&can)!=HAL_OK) board_fatal(2);
filter.IdType=FDCAN_EXTENDED_ID; filter.FilterIndex=0;
filter.FilterType=FDCAN_FILTER_MASK; filter.FilterConfig=FDCAN_FILTER_TO_RXFIFO0;
filter.FilterID1=0xBA0000u+EMU_CAN_MODE-1; filter.FilterID2=0x1fffffffu;
/* Отдельно запрещаем все стандартные, посторонние и remote-кадры. */
if(HAL_FDCAN_ConfigFilter(&can,&filter)!=HAL_OK ||
HAL_FDCAN_ConfigGlobalFilter(&can,FDCAN_REJECT,FDCAN_REJECT,
FDCAN_REJECT_REMOTE,FDCAN_REJECT_REMOTE)!=HAL_OK) board_fatal(2);
can_ready=(HAL_FDCAN_Start(&can)==HAL_OK);
if(!can_ready) clock_fault=2;
}
/* Опрос ограничен одним RX и одним TX за проход. Статический снимок ответа
* сохраняется до завершения всех фрагментов; новый запрос во время ответа не принимается. */
void transport_can_poll(uint32_t now)
{
static uint16_t words[124],count,fragment;
static uint8_t tag,error,pending;
static uint32_t sent_at,mailbox;
FDCAN_RxHeaderTypeDef rx; FDCAN_TxHeaderTypeDef tx={0};
uint8_t data[64]; /* HAL может скопировать до 64 байт чужого FD-кадра до проверки формата. */
if(!can_ready) return;
/* FDCAN не делает bxCAN AutoBusOff. Снятие INIT запускает аппаратное ожидание
* 129 последовательностей recessive перед восстановлением шины. */
if((can.Instance->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 (received<sizeof(rx)) rx[received++]=b;
else { invalid=1; ++uart_overflows; }
last_rx=now;
}
}
/* At >19200 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);
}
}
}
}

View File

@@ -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 || ticks<psc) return 1;
stop(context);
__HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_DMA1_CLK_ENABLE();
__HAL_RCC_DMAMUX1_CLK_ENABLE(); __HAL_RCC_DAC1_CLK_ENABLE(); __HAL_RCC_TIM6_CLK_ENABLE();
gpio.Pin=GPIO_PIN_4; gpio.Mode=GPIO_MODE_ANALOG; gpio.Pull=GPIO_NOPULL;
HAL_GPIO_Init(GPIOA,&gpio);
timer.Instance=TIM6; timer.Init.Prescaler=psc-1; timer.Init.Period=ticks/psc-1;
timer.Init.CounterMode=TIM_COUNTERMODE_UP; timer.Init.AutoReloadPreload=TIM_AUTORELOAD_PRELOAD_DISABLE;
/* Remove TRGO before Base_Init generates its prescaler update event. */
master.MasterOutputTrigger=TIM_TRGO_RESET;
master.MasterSlaveMode=TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&timer,&master)!=HAL_OK ||
HAL_TIM_Base_Init(&timer)!=HAL_OK) return 1;
__HAL_TIM_SET_COUNTER(&timer,0); __HAL_TIM_CLEAR_FLAG(&timer,TIM_FLAG_UPDATE);
master.MasterOutputTrigger=TIM_TRGO_UPDATE;
if (HAL_TIMEx_MasterConfigSynchronization(&timer,&master)!=HAL_OK) return 1;
/* DMA пишет полуслова в фиксированный регистр DAC, последовательно читая таблицу.
* Circular автоматически возвращает адрес к началу; высокий приоритет уменьшает риск underrun. */
dma.Instance=DMA1_Channel1; dma.Init.Request=DMA_REQUEST_DAC1_CHANNEL1;
dma.Init.Direction=DMA_MEMORY_TO_PERIPH; dma.Init.PeriphInc=DMA_PINC_DISABLE;
dma.Init.MemInc=DMA_MINC_ENABLE; dma.Init.PeriphDataAlignment=DMA_PDATAALIGN_HALFWORD;
dma.Init.MemDataAlignment=DMA_MDATAALIGN_HALFWORD; dma.Init.Mode=DMA_CIRCULAR;
dma.Init.Priority=DMA_PRIORITY_HIGH;
if (HAL_DMA_Init(&dma)!=HAL_OK) return 1;
/* Связываем DAC с DMA handle: HAL использует эту связь при запуске и остановке передачи. */
dac.Instance=DAC1; __HAL_LINKDMA(&dac,DMA_Handle1,dma);
if (HAL_DAC_Init(&dac)!=HAL_OK) return 1;
initialized=1;
config.DAC_Trigger=DAC_TRIGGER_T6_TRGO; config.DAC_OutputBuffer=DAC_OUTPUTBUFFER_ENABLE;
if (HAL_DAC_ConfigChannel(&dac,&config,DAC_CHANNEL_1)!=HAL_OK) goto fail;
__HAL_DAC_CLEAR_FLAG(&dac,DAC_FLAG_DMAUDR1);
/* DMA fills the next sample after each trigger, preserving cyclic phase. */
/* Первым триггером выводится заранее записанный последний отсчёт; далее идут 0,1,... из DMA.
* Таймер запускается последним, когда DAC и DMA уже готовы. Приведение к uint32_t* требуется API HAL,
* но размер реальной передачи задаётся DMA как HALFWORD (16 бит). */
if (HAL_DAC_SetValue(&dac,DAC_CHANNEL_1,DAC_ALIGN_12B_R,samples[count-1])!=HAL_OK ||
HAL_DAC_Start_DMA(&dac,DAC_CHANNEL_1,(uint32_t *)samples,(uint32_t)count,DAC_ALIGN_12B_R)!=HAL_OK ||
HAL_TIM_Base_Start(&timer)!=HAL_OK) goto fail;
return 0;
/* Если запуск после инициализации DAC не завершился, возвращаем выход к нулю и сообщаем ошибку. */
fail:
stop(context); return 1;
}
/* Возвращает таблицу callback-функций для общего генератора; само оборудование ещё не запускается. */
set_wave_port wave_hal_port(void)
{
set_wave_port port={0,start,stop}; return port;
}
/* Опрос ошибки канала DMA и underrun DAC. У G4 нет F4 stream FIFO/direct-mode flags.
* NVIC-прерывания DMA/DAC здесь не включены: флаги проверяет основной цикл, остановку выполняет wave_output_poll(). */
int wave_hal_fault(void)
{
return initialized && (__HAL_DMA_GET_FLAG(&dma,DMA_FLAG_TE1) ||
__HAL_DAC_GET_FLAG(&dac,DAC_FLAG_DMAUDR1));
}

View File

@@ -0,0 +1,47 @@
# STM32G474xB USB boot port
SET v2 USB CDC bootloader for STM32G474 with 128 KiB Flash. Uses HSI16 for
the CPU and HSI48 with CRS USB SOF synchronization for USB; no HSE required.
Dedicated USB pins: PA11/PA12. The board must supply VDDUSB correctly.
Compile the shared `src/set_protocol.c`, `src/set_firmware.c`, `src/set_boot.c`,
this port's `boot_main.c`, `boot_flash.c`, `boot_request.c`, `boot_usb_stream.c`
and `boot_usb_port.c`. Supply G4 CMSIS/HAL/startup, ST USB Device CDC and the
board's CDC byte transport (`usb_cdc.h`, `usb_setp_stream.h`).
Copy `boot_config.template.h` as `boot_config.h`. The wrapper uses the common
`../stm32-usb-boot/boot_usb_protocol.inc`; retain that directory when importing.
For the shared emulator CDC transport, set `EMU_USB_SET_V2=1`,
`USB_CDC_IRQn=USB_LP_IRQn` and optionally `USB_CDC_PRODUCT_NAME`.
Provide `usbd_conf.h` using `stm32g4xx_hal.h`. The low-level port allocates
non-overlapping PMA buffers for EP0, CDC IN/OUT and notifications.
Link boot code below `0x0800F000`, reserve `0x0800F000..0x0800FFFF` for metadata,
and link the application at `0x08010000` with VTOR at that address.
Application capacity is 64 KiB, ending at `0x08020000` (exclusive).
Flash size must be 128 KiB and DBANK must be zero (single bank, 4 KiB pages).
BFB2 and FB_MODE bank swapping must be disabled. Unsupported geometry is
rejected before erase. On xB in dual-bank mode the upper 64 KiB starts at
`0x08040000`, leaving a hole at the application's link address. Therefore this
profile deliberately rejects dual-bank mode. Set DBANK=0 via SWD before the
initial installation, then program both bootloader and application afresh.
The port does not modify Option Bytes.
HAL doubleword programming plus an eight-byte staging buffer handles arbitrary
SET block sizes without programming ECC words twice. Only the final word is
padded with FF; CRC covers the declared image size. END checks written Flash
and vectors, commits metadata last; ACTIVATE defers reset by 500 ms for the reply.
The reset policy matches F407: valid vectors alone allow SWD-installed images.
Metadata is not required at reset, so an incomplete image with valid vectors can
also start after an interrupted update. Erase the first application page through
SWD to recover. No rollback, resume or signature authentication is implemented.
Application integration: compile `boot_request.c`, reserve `TAMP->BKP0R`, 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.

View File

@@ -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

View File

@@ -0,0 +1,109 @@
#include "boot_port.h"
#include "boot_image.h"
#include "boot_writer.h"
#include "stm32g4xx_hal.h"
#include <string.h>
#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);
}

View File

@@ -0,0 +1,34 @@
#ifndef G474_BOOT_IMAGE_H
#define G474_BOOT_IMAGE_H
#include "boot_port.h"
#include <string.h>
/* 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

View File

@@ -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); }
}

View File

@@ -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

View File

@@ -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();
}

View File

@@ -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); }

View File

@@ -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"

View File

@@ -0,0 +1,37 @@
#ifndef G474_BOOT_WRITER_H
#define G474_BOOT_WRITER_H
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
/* 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

Some files were not shown because too many files have changed in this diff Show More