1 Commits

56 changed files with 3627 additions and 0 deletions

View File

@@ -11,6 +11,7 @@ set(SETPROTOCOL_V2_SOURCES
src/set_protocol.c
src/set_can.c
src/set_firmware.c
src/set_boot.c
src/set_telemetry.c
src/set_plot.c
src/set_trends.c
@@ -36,6 +37,12 @@ set(SETPROTOCOL_LEGACY_SOURCES
)
set(SETPROTOCOL_SOURCES
src/set_regmap.c
src/set_emu_ump.c
src/set_emu_tms.c
src/set_emu_igbt.c
src/set_emu_server.c
src/set_wavegen.c
${SETPROTOCOL_V2_SOURCES}
${SETPROTOCOL_LEGACY_SOURCES}
)
@@ -81,6 +88,14 @@ endif()
option(SETP_BUILD_TESTS "Build host tests" ON)
if(SETP_BUILD_TESTS)
enable_testing()
add_executable(test_emulator_services tests/test_emulator_services.c ports/example-emulator/service.c)
target_link_libraries(test_emulator_services PRIVATE setprotocol_static)
add_test(NAME emulator_services COMMAND test_emulator_services)
if(MSVC)
target_compile_options(test_emulator_services PRIVATE /UNDEBUG)
else()
target_compile_options(test_emulator_services PRIVATE -UNDEBUG)
endif()
add_executable(test_altera_logic tests/test_altera_logic.c)
target_link_libraries(test_altera_logic PRIVATE setprotocol_static)
add_test(NAME altera_logic COMMAND test_altera_logic)

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

View File

@@ -0,0 +1,97 @@
#include "boot_image.h"
#include "boot_writer.h"
#include <assert.h>
#include <stdio.h>
static uint8_t flash[1024];
static bool touched[128];
static bool fail_write;
static bool program(void *user, uint32_t offset, uint64_t word)
{
(void)user;
assert(!(offset & 7U)); assert(offset <= sizeof(flash) - 8);
assert(!touched[offset / 8]);
if (fail_write) return false;
touched[offset / 8] = true;
memcpy(flash + offset, &word, 8); return true;
}
static void test_writer(void)
{
uint8_t image[521];
uint32_t block, size, i;
for (i = 0; i < sizeof(image); ++i) image[i] = (uint8_t)(i * 19U);
/* Every block size, all possible final-word tails, including all-FF words. */
memset(image + 128, 0xff, 32);
for (block = 1; block <= 256; ++block) {
for (size = 512; size <= sizeof(image); ++size) {
g474_writer w;
memset(flash, 0xff, sizeof(flash)); memset(touched, 0, sizeof(touched));
g474_writer_init(&w, size);
for (i = 0; i < size;) {
uint16_t n = (uint16_t)(size - i < block ? size - i : block);
assert(g474_writer_write(&w, i, image + i, n, program, 0)); i += n;
}
assert(!w.failed && !w.used && w.received == size);
assert(!memcmp(flash, image, size));
for (i = size; i < sizeof(flash); ++i) assert(flash[i] == 0xff);
assert(!g474_writer_write(&w, size, image, 1, program, 0));
}
}
{
g474_writer w;
g474_writer_init(&w, 9);
assert(!g474_writer_write(&w, 1, image, 1, program, 0));
assert(!g474_writer_write(&w, 0, image, 10, program, 0));
assert(!g474_writer_write(&w, 0, image, 0, program, 0));
assert(w.received == 0);
memset(touched, 0, sizeof(touched)); fail_write = true;
assert(!g474_writer_write(&w, 0, image, 8, program, 0));
assert(w.failed);
fail_write = false;
assert(!g474_writer_write(&w, w.received, image, 1, program, 0));
g474_writer_init(&w, 9);
assert(g474_writer_write(&w, 0, image, 9, program, 0));
}
}
static void check_page(uint32_t address, bool dual, uint32_t bank, uint32_t page)
{
uint32_t b, p;
assert(g474_flash_page(address, dual, &b, &p));
assert(b == bank && p == page);
}
int main(void)
{
uint32_t vectors[2] = {0x20018000UL, G474_APP_BASE + 9};
uint32_t b, p;
const uint8_t *image = (const uint8_t *)vectors;
assert(g474_geometry_supported(128, false, false));
assert(!g474_geometry_supported(128, true, false));
assert(!g474_geometry_supported(128, false, true));
assert(!g474_geometry_supported(512, false, false));
assert(!g474_geometry_supported(256, false, false));
assert(!g474_geometry_supported(0xffff, false, false));
check_page(G474_META_BASE, false, 1, 15);
check_page(G474_APP_BASE, false, 1, 16);
check_page(0x0801ffffUL, false, 1, 31);
assert(!g474_flash_page(G474_META_BASE - 1, false, &b, &p));
assert(!g474_flash_page(0x08020000UL, false, &b, &p));
assert(!g474_flash_page(0x08040000UL, false, &b, &p));
assert(!g474_flash_page(G474_META_BASE, true, &b, &p));
assert(!g474_flash_page(G474_APP_BASE, true, &b, &p));
assert(g474_vectors_valid(image, G474_APP_SIZE));
vectors[0] = 0x10008000UL; assert(g474_vectors_valid(image, 16));
vectors[0] = 0x20020000UL; assert(g474_vectors_valid(image, 16));
vectors[0] = 0x20020008UL; assert(!g474_vectors_valid(image, 16));
vectors[0] = 0x10008008UL; assert(!g474_vectors_valid(image, 16));
vectors[0] = 0x20000000UL; assert(!g474_vectors_valid(image, 16));
vectors[0] = 0x20017fffUL; assert(!g474_vectors_valid(image, 16));
vectors[0] = 0x20018000UL;
vectors[1] = G474_APP_BASE + 8; assert(!g474_vectors_valid(image, 16));
vectors[1] = G474_APP_BASE - 1; assert(!g474_vectors_valid(image, 16));
vectors[1] = G474_APP_BASE + 17; assert(!g474_vectors_valid(image, 16));
vectors[1] = G474_APP_BASE + 9;
assert(!g474_vectors_valid(image, 7));
assert(!g474_vectors_valid(image, G474_APP_SIZE + 1));
test_writer();
puts("G474: bank/page boundaries, vectors, all block sizes and ECC word tails passed");
return 0;
}

View File

@@ -0,0 +1,76 @@
#include "set_boot.h"
#include <string.h>
void setp_boot_init(setp_boot *b, const setp_boot_ops *ops, void *user,
uint32_t base, uint32_t capacity)
{
memset(b, 0, sizeof(*b)); b->ops=*ops; b->user=user;
b->base=base; b->capacity=capacity;
b->status.max_block_size=256;
}
static bool zero_hash(const uint8_t *p)
{
unsigned i; for(i=0;i<32;++i) if(p[i]) return false;
return true;
}
static uint16_t command(setp_boot *b, uint16_t type, const uint8_t *p, uint16_t n)
{
setp_fw_begin_t begin;
setp_fw_data_t data;
setp_fw_end_t end;
switch(type) {
case SETP_MSG_FW_BEGIN:
if(!setp_fw_begin_decode(p,n,&begin)) return SETP_STATUS_INVALID_LENGTH;
/* This backend implements CRC integrity only, no resume/signature/SHA. */
if(begin.slot || begin.flags!=SETP_FW_FLAG_ERASE_SLOT ||
begin.signature_length || begin.signing_key_id || !zero_hash(begin.sha256))
return SETP_STATUS_UNSUPPORTED;
if(begin.base_address!=b->base || begin.image_size<8 ||
begin.image_size>b->capacity || begin.block_size>256)
return SETP_STATUS_INVALID_ARGUMENT;
b->status.state=SETP_FW_FAILED;
if(!b->ops.invalidate(b->user) || !b->ops.erase(b->user,begin.image_size))
return SETP_STATUS_INTERNAL;
b->crc=begin.image_crc32; b->block_size=begin.block_size;
b->status.image_size=begin.image_size; b->status.next_offset=0;
b->status.state=SETP_FW_RECEIVING; return SETP_STATUS_OK;
case SETP_MSG_FW_DATA:
if(b->status.state!=SETP_FW_RECEIVING) return SETP_STATUS_WRONG_STATE;
if(!setp_fw_data_decode(p,n,&data)) return SETP_STATUS_CRC;
if(data.flags || data.data_length>b->block_size) return SETP_STATUS_INVALID_ARGUMENT;
if(data.offset!=b->status.next_offset) return SETP_STATUS_SEQUENCE;
if(data.data_length>b->status.image_size-b->status.next_offset)
return SETP_STATUS_NO_SPACE;
if(!b->ops.write(b->user,data.offset,data.data,data.data_length)) {
b->status.state=SETP_FW_FAILED; return SETP_STATUS_INTERNAL;
}
b->status.next_offset+=data.data_length; return SETP_STATUS_OK;
case SETP_MSG_FW_END:
if(b->status.state!=SETP_FW_RECEIVING) return SETP_STATUS_WRONG_STATE;
if(!setp_fw_end_decode(p,n,&end)) return SETP_STATUS_INVALID_LENGTH;
if(!zero_hash(end.sha256)) return SETP_STATUS_UNSUPPORTED;
if(end.image_size!=b->status.image_size || end.image_crc32!=b->crc ||
b->status.next_offset!=b->status.image_size) return SETP_STATUS_SEQUENCE;
b->status.state=SETP_FW_FAILED;
if(!b->ops.verify(b->user,end.image_size,b->crc)) return SETP_STATUS_VERIFY_FAILED;
if(!b->ops.commit(b->user,end.image_size,b->crc)) return SETP_STATUS_INTERNAL;
b->status.state=SETP_FW_READY; return SETP_STATUS_OK;
case SETP_MSG_FW_ABORT:
if(n) return SETP_STATUS_INVALID_LENGTH;
b->status.state=SETP_FW_IDLE; b->status.next_offset=0;
return SETP_STATUS_OK;
case SETP_MSG_FW_STATUS:
return n ? SETP_STATUS_INVALID_LENGTH : SETP_STATUS_OK;
case SETP_MSG_FW_ACTIVATE:
if(n) return SETP_STATUS_INVALID_LENGTH;
if(b->status.state!=SETP_FW_READY) return SETP_STATUS_WRONG_STATE;
b->ops.activate(b->user); return SETP_STATUS_OK;
default: return SETP_STATUS_UNSUPPORTED;
}
}
uint16_t setp_boot_command(setp_boot *b, uint16_t type, const uint8_t *p, uint16_t n)
{
uint16_t result=command(b,type,p,n);
if(type!=SETP_MSG_FW_STATUS) b->status.last_error=result;
return result;
}

View File

@@ -0,0 +1,142 @@
#include "set_emu_igbt.h"
#include <string.h>
static void output(set_igbt_emu *s)
{
unsigned ch;s->rx=0;
if(!s->enabled)return;
for(ch=0;ch<SET_IGBT_CHANNELS;++ch) {
set_igbt_channel *c=&s->channel[ch];uint8_t bit=(uint8_t)(1u<<ch),level;
if(!(c->flags&2))continue;
if(c->mode==1)level=0;
else if(c->mode==2)level=1;
else {level=(uint8_t)(c->mode==3?0:!!(s->logical_rx&bit));if(c->flags&4)level^=1;}
if(level)s->rx|=bit;
}
}
void set_igbt_init(set_igbt_emu *s)
{
unsigned ch;memset(s,0,sizeof(*s));s->enabled=1;s->delay_ms=1;s->width_ms=1;
for(ch=0;ch<SET_IGBT_CHANNELS;++ch) {
set_igbt_channel *c=&s->channel[ch];
c->flags=3;c->on_delay=c->off_delay=1;c->on_width=c->off_width=1;
c->tx_width=10;c->edges=3;
}
}
void set_igbt_tick(set_igbt_emu *s,uint8_t pins)
{
unsigned ch;uint8_t changed;
for(ch=0;ch<SET_IGBT_CHANNELS;++ch)
if(s->pulse[ch]&&--s->pulse[ch]==0)s->injected_tx&=(uint8_t)~(1u<<ch);
s->tx=s->input_source?s->injected_tx:pins;
for(ch=0;ch<SET_IGBT_CHANNELS;++ch)
if(!(s->channel[ch].flags&1))s->tx&=(uint8_t)~(1u<<ch);
changed=(uint8_t)(s->tx^s->previous_tx);s->previous_tx=s->tx;
if(!s->enabled){output(s);return;}
for(ch=0;ch<SET_IGBT_CHANNELS;++ch) {
set_igbt_channel *c=&s->channel[ch];uint8_t bit=(uint8_t)(1u<<ch);
unsigned on=!!(s->tx&bit);
if(s->active[ch]&&(!(s->fault&bit)||c->fault_width)&&--s->active[ch]==0)
s->logical_rx&=(uint8_t)~bit;
if(s->pending[ch]&&--s->pending[ch]==0) {
s->logical_rx|=bit;s->active[ch]=s->response_width[ch];
}
if(changed&bit) {
++s->event_count;++c->count;
if(!(c->flags&1)||!(c->flags&2)||c->mode||!(c->edges&(on?1u:2u)))continue;
s->pending[ch]=on?c->on_delay:c->off_delay;
s->response_width[ch]=(s->fault&bit)&&c->fault_width?c->fault_width:(on?c->on_width:c->off_width);
if(!s->pending[ch]){s->logical_rx|=bit;s->active[ch]=s->response_width[ch];}
}
}
output(s);
}
unsigned set_igbt_read(void *ctx,uint16_t off,uint16_t count,uint16_t *out)
{
set_igbt_emu *s=(set_igbt_emu *)ctx;
uint16_t w[SET_IGBT_WORDS]={0};unsigned i,ch;
if(!s||!out||!count||(uint32_t)off+count>SET_IGBT_WORDS)return 2;
w[0]=0x4947;w[1]=3;w[2]=s->enabled;w[3]=s->tx;w[4]=s->rx;
w[6]=s->fault;w[7]=s->delay_ms;w[8]=s->width_ms;w[9]=s->event_count;
w[10]=8;w[11]=1;w[12]=s->input_source;w[13]=s->injected_tx;
w[15]=SET_IGBT_CHANNEL_WORDS;
for(ch=0;ch<SET_IGBT_CHANNELS;++ch) {
set_igbt_channel *c=&s->channel[ch];unsigned b=16+ch*SET_IGBT_CHANNEL_WORDS;
w[b]=c->flags;w[b+1]=c->mode;w[b+2]=c->on_delay;w[b+3]=c->on_width;
w[b+4]=c->off_delay;w[b+5]=c->off_width;w[b+6]=c->tx_width;
w[b+7]=c->edges;w[b+8]=c->fault_width;w[b+9]=c->count;
w[b+10]=s->pending[ch];w[b+11]=s->active[ch];
}
for(i=0;i<count;++i)out[i]=w[off+i];
return 0;
}
unsigned set_igbt_write(void *ctx,uint16_t off,uint16_t value)
{
set_igbt_emu *s=(set_igbt_emu *)ctx;unsigned ch;uint8_t released;
if(!s)return 3;
if(off>=16) {
unsigned field=(off-16)%SET_IGBT_CHANNEL_WORDS;
set_igbt_channel *c;
ch=(off-16)/SET_IGBT_CHANNEL_WORDS;if(ch>=8)return 2;c=&s->channel[ch];
switch(field) {
case 0:if(value>7)return 3;c->flags=value;break;
case 1:if(value>3)return 3;c->mode=value;break;
case 2:if(value>1000)return 3;c->on_delay=value;break;
case 3:if(!value||value>1000)return 3;c->on_width=value;break;
case 4:if(value>1000)return 3;c->off_delay=value;break;
case 5:if(!value||value>1000)return 3;c->off_width=value;break;
case 6:if(!value||value>60000)return 3;c->tx_width=value;break;
case 7:if(value>3)return 3;c->edges=value;break;
case 8:if(value>60000)return 3;c->fault_width=value;break;
default:return 2;
}
/* Mode/enable changes discard pending responses. */
if(field<=1) {s->pending[ch]=s->active[ch]=0;s->logical_rx&=(uint8_t)~(1u<<ch);}
output(s);return 0;
}
switch(off) {
case 2:
if(value>1)return 3;
s->enabled=(uint8_t)value;
if(!value) {s->logical_rx=0;
for(ch=0;ch<8;++ch)if(s->pulse[ch])s->injected_tx&=(uint8_t)~(1u<<ch);
memset(s->pending,0,sizeof(s->pending));
memset(s->active,0,sizeof(s->active));memset(s->pulse,0,sizeof(s->pulse));}
break;
case 3:case 13:
if(value>255)return 3;
s->injected_tx=(uint8_t)value;memset(s->pulse,0,sizeof(s->pulse));break;
case 6:
if(value>255)return 3;
released=(uint8_t)(s->fault&~value);s->fault=(uint8_t)value;
s->logical_rx&=(uint8_t)~released;
for(ch=0;ch<8;++ch)if(released&(1u<<ch))s->active[ch]=0;
break;
case 7:
if(value>1000)return 3;
s->delay_ms=value;
for(ch=0;ch<8;++ch)s->channel[ch].on_delay=s->channel[ch].off_delay=value;
break;
case 8:
if(!value||value>255)return 3;
s->width_ms=value;
for(ch=0;ch<8;++ch)s->channel[ch].on_width=s->channel[ch].off_width=value;
break;
case 9:
if(value)return 3;
s->event_count=0;for(ch=0;ch<8;++ch)s->channel[ch].count=0;break;
case 12:
if(value>1)return 3;
s->input_source=(uint8_t)value;memset(s->pulse,0,sizeof(s->pulse));break;
case 14:
if(value>255)return 3;
if(!s->enabled||!s->input_source)return 6;
/* A pulse must start from low; retrigger without an edge is refused. */
if(value&(s->injected_tx|s->tx))return 6;
for(ch=0;ch<8;++ch)if((value&(1u<<ch))&&!(s->channel[ch].flags&1))return 6;
s->injected_tx|=(uint8_t)value;
for(ch=0;ch<8;++ch)if(value&(1u<<ch))s->pulse[ch]=(uint16_t)(s->channel[ch].tx_width+1u);
break;
default:return 2;
}
output(s);return 0;
}

View File

@@ -0,0 +1,194 @@
#include "set_emu_server.h"
#include "set_crc.h"
#include <string.h>
static const uint32_t baudrates[5]={9600,19200,38400,57600,115200};
uint32_t set_emu_server_baud(const set_emu_server *s) { return baudrates[s->config[2]]; }
static unsigned control_read(void *ctx,uint16_t off,uint16_t n,uint16_t *out)
{
set_emu_server *s=(set_emu_server *)ctx;uint16_t w[36]={0};unsigned i;
if(!((off==0&&n==4)||(off==16&&(n==8||n==10))||(off==32&&n==4)))return 2;
w[0]=0x454d;w[1]=1;w[2]=s->ump.enabled&1u;w[3]=s->ump.prev_command;
w[16]=0x5343;w[17]=3;
for(i=0;i<3;++i){w[18+i]=s->config[i];w[21+i]=s->staged[i];}
w[24]=s->config[3];w[25]=s->staged[3];
w[32]=0x4642;w[33]=1;w[34]=3;w[35]=SET_UMP_WORDS;
for(i=0;i<n;++i)out[i]=w[off+i];
return 0;
}
static unsigned control_write(void *ctx,uint16_t off,uint16_t v)
{
set_emu_server *s=(set_emu_server *)ctx;
if(off==2) {
if(v && s->config[3]!=0)return 6;
if(v>1)return 3;
s->ump.enabled=(uint8_t)((s->ump.enabled&~1u)|v);
if(!v)s->ump.scenario=0;
return 0;
}
if(off<21||off>25)return 2;
if(s->apply_delay)return 6;
if(off==24) {
if(v==0)memcpy(s->staged,s->config,sizeof(s->config));
else if(v==0xa55a) {
if(s->staged[0]==s->staged[1])return 3;
s->apply_delay=500;
} else return 3;
} else if(off==25) {
if(v>3 || (v==3&&!s->wave))return 3;
s->staged[3]=v;
} else {
if((off==21&&(!v||v>247))||(off==22&&(!v||v>255))||(off==23&&v>4))return 3;
s->staged[off-21]=v;
}
return 0;
}
static unsigned ump_read(void *ctx,uint16_t off,uint16_t n,uint16_t *out)
{
set_emu_server *s=(set_emu_server *)ctx;
return s->config[3]?6:set_ump_read(&s->ump,off,n,out);
}
static unsigned ump_write(void *ctx,uint16_t off,uint16_t v)
{
set_emu_server *s=(set_emu_server *)ctx;
return s->config[3]?6:set_ump_write(&s->ump,off,v);
}
static unsigned indexed_read(void *ctx,uint16_t off,uint16_t n,uint16_t *out)
{
set_emu_server *s=(set_emu_server *)ctx;
return s->config[3]?6:set_ump_indexed_read(&s->ump,off,n,out);
}
static unsigned tms_read(void *ctx,uint16_t off,uint16_t n,uint16_t *out)
{
set_emu_server *s=(set_emu_server *)ctx;
return set_tms_read(&s->tms,off,n,out);
}
static unsigned tms_write(void *ctx,uint16_t off,uint16_t v)
{
set_emu_server *s=(set_emu_server *)ctx;
return s->config[3]!=1?6:set_tms_write(&s->tms,off,v);
}
/* Status remains readable in every mode; only the selected service may write.
* Explicit stop commands remain available regardless of mode. */
static unsigned igbt_write(void *ctx,uint16_t off,uint16_t v)
{
set_emu_server *s=(set_emu_server *)ctx;
if(s->config[3]!=2 && !(off==2&&v==0))return 6;
return set_igbt_write(&s->igbt,off,v);
}
static unsigned igbt_read(void *ctx,uint16_t off,uint16_t n,uint16_t *out)
{
return set_igbt_read(&((set_emu_server *)ctx)->igbt,off,n,out);
}
static unsigned wave_write(void *ctx,uint16_t off,uint16_t v)
{
set_emu_server *s=(set_emu_server *)ctx;
if(s->config[3]!=3 && !(off==2&&v==0))return 6;
return set_wave_write(s->wave,off,v);
}
static unsigned wave_read(void *ctx,uint16_t off,uint16_t n,uint16_t *out)
{
return set_wave_read(((set_emu_server *)ctx)->wave,off,n,out);
}
static unsigned samples_write(void *ctx,uint16_t off,uint16_t v)
{
set_emu_server *s=(set_emu_server *)ctx;
return s->config[3]!=3?6:set_wave_samples_write(s->wave,off,v);
}
static unsigned samples_read(void *ctx,uint16_t off,uint16_t n,uint16_t *out)
{
return set_wave_samples_read(((set_emu_server *)ctx)->wave,off,n,out);
}
static void region(set_reg_region *r,uint16_t base,uint16_t words,uint16_t kind,
uint16_t version,void *ctx,set_reg_read_fn read,set_reg_write_fn write)
{
r->base=base;r->words=words;r->kind=kind;r->version=version;r->instance=0;
r->flags=(uint16_t)(SET_REG_READ|(write?SET_REG_WRITE:0));
r->context=ctx;r->read=read;r->write=write;
}
unsigned set_emu_server_init(set_emu_server *s,uint16_t *b0,uint16_t *b1,
uint16_t capacity,set_wave_state *wave,unsigned address,unsigned tms,
unsigned baud,unsigned can,unsigned enabled)
{
size_t n=0;
if(!s||!b0||!b1||b0==b1||capacity<2||capacity>819||
!address||address>247||!tms||tms>255||address==tms||baud>4||
!can||can>16||enabled>1)return 3;
if(wave&&(!wave->samples||wave->capacity<2||wave->capacity>SET_WAVE_MAX))return 3;
memset(s,0,sizeof(*s));s->wave=wave;
if(set_ump_init(&s->ump,b0,b1,capacity,(uint16_t)can,(uint8_t)enabled))return 3;
set_tms_init(&s->tms);set_igbt_init(&s->igbt);s->igbt.enabled=0;
s->config[0]=(uint16_t)address;s->config[1]=(uint16_t)tms;s->config[2]=(uint16_t)baud;
memcpy(s->staged,s->config,sizeof(s->config));
region(&s->regions[n++],0x1000,0x17c,SET_EMU_KIND_UMP,2,s,ump_read,ump_write);
region(&s->regions[n++],SET_EMU_CONTROL_BASE,36,SET_EMU_KIND_CONTROL,3,s,control_read,control_write);
region(&s->regions[n++],SET_EMU_IGBT_BASE,SET_IGBT_WORDS,SET_EMU_KIND_IGBT,3,s,igbt_read,igbt_write);
region(&s->regions[n++],SET_EMU_TMS_BASE,SET_TMS_WORDS,SET_EMU_KIND_TMS,1,s,tms_read,tms_write);
region(&s->regions[n++],SET_EMU_INDEXED_BASE,(uint16_t)(capacity*SET_UMP_WORDS),
SET_EMU_KIND_UMP_DATA,1,s,indexed_read,0);
if(wave) {
region(&s->regions[n++],SET_WAVE_BASE,9,SET_EMU_KIND_WAVE,3,s,wave_read,wave_write);
region(&s->regions[n++],SET_WAVE_DATA,(uint16_t)wave->capacity,
SET_EMU_KIND_WAVE_DATA,1,s,samples_read,samples_write);
}
return set_regmap_init(&s->map,s->regions,n);
}
void set_emu_server_tick(set_emu_server *s,uint8_t pins)
{
if(s->apply_delay&&--s->apply_delay==0) {
if(s->config[3]!=s->staged[3]) {
/* Stop every executor before selecting the new one. No implicit start. */
if(s->wave) {
if(s->wave->port.stop)s->wave->port.stop(s->wave->port.context);
s->wave->running=0;s->wave->ready=0;
}
set_igbt_init(&s->igbt);s->igbt.enabled=0;
set_tms_init(&s->tms);s->ump.state=3;s->ump.post=0;s->ump.scenario=0;
s->ump.prev_command=0;s->ump.prev_output=0;
}
memcpy(s->config,s->staged,sizeof(s->config));
}
if(s->config[3]==1)set_tms_tick(&s->tms);
else if(s->config[3]==0)set_ump_tick(&s->ump);
else if(s->config[3]==2)set_igbt_tick(&s->igbt,pins);
}
static int inactive_ump(const set_emu_server *s,uint16_t a)
{
return s->config[3]&&((a>=0x1000&&a<0x1200)||
(a>=SET_EMU_INDEXED_BASE&&a<(uint32_t)SET_EMU_INDEXED_BASE+s->ump.capacity*SET_UMP_WORDS));
}
size_t set_emu_server_rtu(set_emu_server *s,const uint8_t *r,size_t n,uint8_t *out,size_t cap)
{
if(!s||!r||!n)return 0;
if(r[0]==s->config[1])
return s->config[3]==1?set_tms_rtu(&s->tms,s->config[1],r,n,out,cap):0;
if(n==8&&inactive_ump(s,(uint16_t)(((unsigned)r[2]<<8)|r[3])))return 0;
return set_regmap_rtu(&s->map,s->config[0],r,n,out,cap);
}
size_t set_emu_server_can(set_emu_server *s,const uint8_t *r,size_t n,
uint16_t *out,size_t cap,unsigned *token,unsigned *error)
{
if(token)*token=0;
if(error)*error=0;
if(!s||!r||n!=8||inactive_ump(s,(uint16_t)(((unsigned)r[0]<<8)|r[1])))return 0;
return set_regmap_can(&s->map,r,n,out,cap,token,error);
}
size_t set_emu_stream_feed(set_emu_stream *st,set_emu_server *s,uint8_t byte,
uint32_t now,uint8_t *out,size_t cap)
{
size_t length,n;
if(!st||!s||!out||cap<253)return 0;
if((uint32_t)(now-st->last_byte)>1000u||st->used>=sizeof(st->data))st->used=0;
st->last_byte=now;st->data[st->used++]=byte;
while(st->used>=8) {
length=st->data[0]==s->config[1]&&st->data[1]==65?18u:8u;
if(st->used<length)return 0;
if(set_crc16_modbus(st->data,length)) {
--st->used;memmove(st->data,st->data+1,st->used);continue;
}
n=set_emu_server_rtu(s,st->data,length,out,cap);
st->used=(uint8_t)(st->used-length);
memmove(st->data,st->data+length,st->used);
if(n)return n;
}
return 0;
}

View File

@@ -0,0 +1,96 @@
#include "set_emu_tms.h"
#include "set_crc.h"
#include <string.h>
static void analog(uint8_t *reply, unsigned index, int32_t value)
{
uint16_t word = (uint16_t)value;
reply[30 + index*2] = (uint8_t)word;
reply[31 + index*2] = (uint8_t)(word >> 8);
}
void set_tms_init(set_tms_emu *s) { memset(s,0,sizeof(*s)); }
void set_tms_tick(set_tms_emu *s)
{
int32_t wanted = s->running ? s->target : 0;
/* 10 Hz/s, signed hundredths of Hz. Independent of UMP recorder. */
if (++s->divider < 10) return;
s->divider = 0;
if (s->frequency < wanted) s->frequency += wanted-s->frequency < 10 ? wanted-s->frequency : 10;
if (s->frequency > wanted) s->frequency -= s->frequency-wanted < 10 ? s->frequency-wanted : 10;
}
static void apply(set_tms_emu *s)
{
unsigned i;uint16_t word=s->commands[0];
for(i=0;i<6;++i)s->controls[i]=(uint8_t)s->commands[4+i];
s->running=(uint8_t)((s->controls[0]&0x41)==0x41);
if((s->controls[0]&0x40) && (s->controls[2]&4))
s->target=word<0x8000?(int32_t)word:(int32_t)word-65536;
}
static void snapshot(set_tms_emu *s,uint8_t *reply)
{
memset(reply, 0, 101);
reply[0] = 0; reply[1] = 65;
/* BALZAM_7: byte10.Go; ready/Go LED; remote selector. */
reply[11] = s->running;
reply[14] = (uint8_t)(0x0b | (s->running << 5));
reply[15] = (uint8_t)(6 | ((s->controls[0] & 0x40) ? 0x20 : 0));
reply[16] = (uint8_t)(3 | ((s->controls[4] & 0xc0) >> 3));
analog(reply, 0, s->frequency); analog(reply, 1, 2000);
analog(reply, 4, s->frequency); analog(reply, 5, s->frequency);
analog(reply, 6, 540); analog(reply, 7, 540);
analog(reply, 8, s->frequency / 10); analog(reply, 9, s->frequency / 10);
analog(reply, 10, s->frequency); analog(reply, 11, s->frequency < 0 ? -1 : 1);
analog(reply, 12, 540); analog(reply, 13, 540);
analog(reply, 14, s->frequency); analog(reply, 15, s->frequency);
analog(reply, 25, 25); analog(reply, 26, 30);
analog(reply, 28, 400); analog(reply, 29, 400);
analog(reply, 30, (s->controls[4] & 0x40) ? 120 : 40);
analog(reply, 31, (s->controls[4] & 0x40) ? 120 : 40);
analog(reply, 32, (s->controls[4] & 0x80) ? 120 : 40);
analog(reply, 33, (s->controls[4] & 0x80) ? 120 : 40);
}
unsigned set_tms_read(void *ctx,uint16_t off,uint16_t count,uint16_t *out)
{
set_tms_emu *s=(set_tms_emu *)ctx;uint8_t r[101];unsigned i,a;
if(!s || !out || !count || (uint32_t)off+count>SET_TMS_WORDS)return 2;
snapshot(s,r);
for(i=0;i<count;++i) {
uint16_t v=0;a=off+i;
if(a==0)v=0x544d;
else if(a==1)v=1;
else if(a==2)v=s->running;
else if(a==3)v=(uint16_t)s->frequency;
else if(a==4)v=(uint16_t)s->target;
else if(a>=8 && a<18)v=s->commands[a-8];
else if(a>=32 && a<46)v=(uint16_t)(r[2+(a-32)*2]|((unsigned)r[3+(a-32)*2]<<8));
else if(a>=46)v=(uint16_t)(r[30+(a-46)*2]|((unsigned)r[31+(a-46)*2]<<8));
out[i]=v;
}
return 0;
}
unsigned set_tms_write(void *ctx,uint16_t off,uint16_t v)
{
set_tms_emu *s=(set_tms_emu *)ctx;
if(!s)return 3;
if(off>=8 && off<18) {
if(off>=12 && v>255)return 3;
s->commands[off-8]=v;return 0;
}
if(off==18) { if(v!=1)return 3;apply(s);return 0; }
return 2;
}
size_t set_tms_rtu(set_tms_emu *s,unsigned device,const uint8_t *r,size_t n,
uint8_t *out,size_t cap)
{
unsigned i;uint16_t crc;
if(!s || !r || !out || n!=18 || cap<101 || !device || device>255 ||
r[0]!=device || r[1]!=65 || set_crc16_modbus(r,n))return 0;
for(i=0;i<4;++i)s->commands[i]=(uint16_t)(r[2+2*i]|((unsigned)r[3+2*i]<<8));
for(i=0;i<6;++i)s->commands[4+i]=r[10+i];
apply(s);snapshot(s,out);out[0]=(uint8_t)device;
crc=set_crc16_modbus(out,98);out[98]=(uint8_t)crc;out[99]=(uint8_t)(crc>>8);
return 101;
}

View File

@@ -0,0 +1,172 @@
/* UMP synthetic signals and recorder. Storage belongs to the caller. */
#include "set_emu_ump.h"
#include <string.h>
static int triangle(uint32_t t, unsigned cycle)
{
int x = (int)(t % cycle) * 480 / (int)cycle;
return x < 240 ? x - 120 : 360 - x;
}
static void signals(set_ump_emu *s, uint16_t *p)
{
unsigned enabled = (s->enabled & 1u) != 0;
unsigned t = enabled ? s->scenario % 6000u : 0, ch;
unsigned run = t >= 1000 && t < 5000, reset = t >= 5000 && t < 5300;
unsigned ramp = t < 1000 ? 0 : (t < 3500 ? t - 1000 : 2500);
unsigned current = run ? 60 + ramp * 100 / 2500 :
(t >= 5000 && t < 5300 ? 40 + (5300-t)*120/300 : 40);
memset(p, 0, SET_UMP_WORDS * sizeof(*p));
p[0] = (uint16_t)s->elapsed; p[1] = (uint16_t)(s->elapsed >> 16);
p[3] = (uint16_t)((run << 7) | (reset << 6));
p[4] = (uint16_t)((t >= 1200 && t < 5100) | (run << 1) | (reset << 2));
p[5] = (uint16_t)((t >= 1000 && t < 2000) | ((t >= 5000) << 1) | (run << 2));
p[6] = (uint16_t)((t >= 1000 && t < 2000) ? 2000-t : (t >= 5000 ? 6000-t : 0));
p[7] = (uint16_t)(enabled && t < 250 ? 250-t : 0);
p[8] = (uint16_t)(500 + (current-40)*3000/160); p[9] = (uint16_t)current;
for (ch = 0; ch < 4; ++ch) {
int v = enabled ? triangle(s->scenario + ch * 211u, 720 + ch * 280u) : 0;
p[10+ch] = (uint16_t)v;
p[14+ch] = (uint16_t)((t >= 5100 && t < 5400) ? 1u << ch : 0);
p[36+ch] = (uint16_t)(2048 + v * 4);
}
/* These fields are synthetic CAN diagnostics, not transport counters. */
p[18] = 0xE07F; p[19] = p[3]; p[20] = 6; p[21] = 16;
p[22] = enabled ? (uint16_t)(s->scenario/100) : 0;
p[24] = 6; p[25] = 16; p[26] = 500; p[27] = 3500;
p[28] = 2000; p[29] = 500; p[30] = 3500;
p[31] = (uint16_t)(run ? ramp : 0); p[32] = 2500;
p[33] = 60; p[34] = 160; p[35] = (uint16_t)(ramp*1000/2500);
}
static void capture(set_ump_emu *s, uint16_t reason, uint16_t *p)
{
if (reason != 5 && !s->post && (((p[3]&0x40) && !(s->prev_command&0x40)) ||
(!(p[3]&0x80) && (s->prev_command&0x80)))) {
if (s->used >= s->capacity/2) s->used = s->capacity/2-1;
s->post = 1000; s->dropped = 0; reason |= 0x100;
}
s->prev_command = p[3]; s->prev_input = p[4]; s->prev_output = p[5];
if (s->post && s->used >= s->capacity-1) { s->dropped = 1; return; }
p[2] = (uint16_t)(reason | ((reason == 5 && s->dropped) ? 0x200 : 0));
memcpy(s->banks[s->bank][s->next], p, SET_UMP_WORDS*sizeof(*p));
s->next = (uint16_t)((s->next+1)%s->capacity);
if (s->used < s->capacity) ++s->used;
++s->sequence;
}
unsigned set_ump_init(set_ump_emu *s,uint16_t *bank0,uint16_t *bank1,
uint16_t capacity,uint16_t can_mode,uint8_t enabled)
{
if(!s || !bank0 || !bank1 || bank0==bank1 || capacity<2 ||
capacity>1638 || !can_mode || can_mode>16)return 3;
memset(s,0,sizeof(*s));
s->banks[0]=(uint16_t (*)[SET_UMP_WORDS])bank0;
s->banks[1]=(uint16_t (*)[SET_UMP_WORDS])bank1;
s->capacity=capacity;s->can_mode=can_mode;s->enabled=enabled;
s->archive_bank=1;s->period=20;return 0;
}
void set_ump_tick(set_ump_emu *s)
{
uint16_t p[SET_UMP_WORDS],old_post;
if (!(s->enabled & 1u)) s->scenario = 0;
if (s->state != 1) return;
++s->elapsed;
if (s->enabled & 1u) ++s->scenario;
old_post = s->post;
signals(s,p);
if (++s->divider >= s->period) { s->divider = 0; capture(s,1,p); }
if (p[3] != s->prev_command || p[4] != s->prev_input || p[5] != s->prev_output) capture(s,3,p);
/* Count a full 1000 ms AFTER the trigger; never extend on new edges. */
if (old_post && --s->post == 0) {
capture(s,5,p);
s->archive_bank = s->bank; s->archive_count = s->used; s->archive_next = s->next;
++s->generation; s->bank ^= 1; s->used = s->next = 0;
}
}
static uint16_t record_word(set_ump_emu *s, unsigned archived, unsigned record, unsigned word)
{
unsigned n = archived ? s->archive_count : s->used;
unsigned end = archived ? s->archive_next : s->next;
if (record >= n) return 0;
return s->banks[archived ? s->archive_bank : s->bank][(end+s->capacity-n+record)%s->capacity][word];
}
static uint16_t read_word(set_ump_emu *s,uint16_t address)
{
unsigned offset,count=s->used<3?s->used:3;
uint32_t first=s->sequence-count;
switch(address) {
case 0x1000: return 0x554D; case 0x1001: return 2;
case 0x1002: return s->source ? 3 : s->state;
case 0x1003: return s->source ? s->archive_count : s->used;
case 0x1004: return SET_UMP_WORDS; case 0x1005: return s->capacity;
case 0x1006: return s->period; case 0x1007: return (uint16_t)s->elapsed;
case 0x1008: return (uint16_t)(s->elapsed>>16); case 0x1009: return 6;
case 0x100A: return s->can_mode; case 0x100B: return 1000;
case 0x100C: return s->generation; case 0x100D: return s->archive_count;
case 0x100E: return s->post; case 0x100F: return s->state;
}
if (address >= 0x1010u && address < 0x1010u+120) {
if (!s->source && s->state == 1) return 0;
offset = address-0x1010u;
return record_word(s,s->source, s->cursor+offset/SET_UMP_WORDS, offset%SET_UMP_WORDS);
}
offset = address-0x1100u;
if (offset == 0) return (uint16_t)first;
if (offset == 1) return (uint16_t)(first>>16);
if (offset == 2) return (uint16_t)count;
if (offset == 3) return s->generation;
offset -= 4;
return offset/SET_UMP_WORDS < count ? record_word(s,0,s->used-count+offset/SET_UMP_WORDS,offset%SET_UMP_WORDS) : 0;
}
unsigned set_ump_read(void *ctx,uint16_t off,uint16_t count,uint16_t *out)
{
set_ump_emu *s=(set_ump_emu *)ctx;unsigned i;
uint32_t end=(uint32_t)off+count;
if(!s || !out || !count || count>124)return 3;
if(!((off<16 && end<=16) || (off>=0x10 && end<=0x88) ||
(off==0x100 && count==124)))return 2;
for(i=0;i<count;++i)out[i]=read_word(s,(uint16_t)(0x1000u+off+i));
return 0;
}
unsigned set_ump_indexed_read(void *ctx,uint16_t off,uint16_t count,uint16_t *out)
{
set_ump_emu *s=(set_ump_emu *)ctx;unsigned i,records;
if(!s || !out || !count || count>120 || off%SET_UMP_WORDS || count%SET_UMP_WORDS)return 3;
records=s->source?s->archive_count:s->used;
if(off/SET_UMP_WORDS+count/SET_UMP_WORDS>records)return 2;
if(!s->source && s->state==1)return 6;
for(i=0;i<count;++i)out[i]=record_word(s,s->source,(off+i)/SET_UMP_WORDS,(off+i)%SET_UMP_WORDS);
return 0;
}
unsigned set_ump_write(void *ctx,uint16_t off,uint16_t value)
{
set_ump_emu *s=(set_ump_emu *)ctx;
uint16_t address=(uint16_t)(0x1000u+off),p[SET_UMP_WORDS];
if(!s || off>3)return 2;
if (address < 0x1000u || address > 0x1000u+3) return 2;
switch (address) {
case 0x1000u:
if (value == 0) { s->state = 3; s->post = 0; }
else if (value == 1 && s->state != 1) {
s->used = s->next = s->cursor = s->divider = s->post = s->source = s->dropped = 0;
s->scenario = 0; s->state = 1;
signals(s,p); s->prev_command = p[3]; capture(s,1,p);
} else return 3;
break;
case 0x1000u+1:
if (s->state == 1 || value < 1 || value > 1000) return 3;
s->period = value; break;
case 0x1000u+2:
if (value >= s->capacity) return 3;
s->cursor = value; break;
case 0x1000u+3:
if (value > 1) return 3;
s->source = value; break;
}
return 0;
}

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