feat: add M95M02-only GAS and EEPROM profiles

Expose the existing registers through Modbus and service CAN using one GAS provider only in M95M02 builds.
Include the EEPROM layout/driver, LOAD protection and calibration, firmware identity and shared CCS build workflow.
Add transport/profile and SCI8 validation tests, documentation and the checked M95M02 firmware image.
This commit is contained in:
2026-10-09 16:17:37 +03:00
parent c6c9128238
commit 5d9818291b
63 changed files with 2307 additions and 365 deletions
+4
View File
@@ -7,3 +7,7 @@
/Bin/Rs232NoFifoTest/
/Bin/UartFifoTest/
/Bin/*.pending
/UKSSTMS320F28335/Generated/
/Bin/FirmwareIdentity/
/Bin/*.bin.manifest.json
/Bin/*.bin.sha256
Binary file not shown.
@@ -0,0 +1,41 @@
{
"schema": 1,
"product": "Balsam-167-PM35",
"target": "TMS320F28335",
"version": [
1,
1,
2
],
"build_id": "c6c9128+",
"built_at_utc": "2026-10-09T13:10:25+00:00",
"source": {
"commit": "c6c91282387adc789a7ebbc6f7e763d02b549789",
"dirty": true
},
"templates": {
"commit": "86ece3ea907cc92db73390eaae7551dd01b54e2c",
"dirty": true
},
"identity_contract": 1,
"identity_register_count": 12,
"eeprom": "M95M02",
"eeprom_bytes": 262144,
"firmware_limit_bytes": 261884,
"gas": {
"enabled": true,
"version": 1,
"profile": 167,
"info_words": 8,
"register_count": 128,
"transports": [
"modbus_rtu",
"ump_service_can"
],
"info_base": 24320,
"register_base": 20480
},
"file": "UKSSTMS320F28335_GAS_M95M02.bin",
"size_bytes": 33174,
"sha256": "a1c186e5f114ea2f77ac5f18a60424c79f53b1e3a49e0343ac9bd96979f0347e"
}
@@ -0,0 +1 @@
a1c186e5f114ea2f77ac5f18a60424c79f53b1e3a49e0343ac9bd96979f0347e UKSSTMS320F28335_GAS_M95M02.bin
+16
View File
@@ -0,0 +1,16 @@
[
{
"after_bytes": 32472,
"profile": 0,
"gas_added_bytes": 0,
"binary_identical": true,
"before_bytes": 32472
},
{
"after_bytes": 33174,
"profile": 1,
"gas_added_bytes": 410,
"binary_identical": false,
"before_bytes": 32764
}
]
+136
View File
@@ -0,0 +1,136 @@
# EEPROM 167: выбор AT25256B / M95M02
Маркировка платы `ATMLH516 5EBL` определяет AT25256B: **256 Кбит**, то есть
**32 768 байт**. Расшифровка и характеристики приведены в
[даташите Microchip](https://ww1.microchip.com/downloads/aemDocuments/documents/MPD/ProductDocuments/DataSheets/AT25128B-AT25256B-128-256-Kbit-SPI-Serial-EEPROM-Industrial-Grade-Data-Sheet-DS20006193.pdf).
Константы общей разметки находятся в
[templates/include/balsam167_eeprom_layout.h](../../newProject/SETGUI/third_party/templates/c/set-protocol/include/balsam167_eeprom_layout.h).
`spise2p.h` использует их для физического размера, количества слов и длины
страницы. `message.c` — для адреса параметров. Размер массива параметров
проверяется относительно `ANSWER_LEN` на этапе компиляции.
`F28335.cmd` включает тот же заголовок и экспортирует символы `_EEPROM_*` в
`.map`. Путь к templates/c/set-protocol добавлен в search path линкера CLI и CCS.
EEPROM не стала секцией CPU MEMORY: SPI-образ загружается в RAM загрузчиком.
Расположение `.text`, `.cinit`, `.econst`, `.switch` не менялось.
| Область | Начало, байт | Конец, исключая | Размер |
|---|---:|---:|---:|
| Прошивка SCI8 | `0x0000` | `0x7EFC` | 32 508 |
| Параметры | `0x7EFC` | `0x7FFE` | 258 |
| Неиспользуемое слово | `0x7FFE` | `0x8000` | 2 |
`check_firmware.ps1` получает размер и резерв из общего заголовка и сверяет
резерв с `ANSWER_LEN`. При нестандартном checkout передайте `-TemplatesRoot`;
`build_logger.ps1` делает это автоматически. Исторические параметры не перенесены.
Чтение с платы, единицы адресов, порядок байтов и сохранение дампа описаны в
[руководстве SETGUI](../../newProject/SETGUI/docs/BALSAM167_EEPROM.md).
Для старой прошивки reader использует CMD_PEEK (2 байта за запрос).
Версия 0.1.1 также поддерживает блочное чтение FC03, регистры 0x8000..0xBFFF,
до 120 слов (240 байт) за запрос. Полный дамп требует 137 запросов.
Границы и формат заданы в templates, GUI использует тонкий Python-порт.
Блочное чтение выполняется с разрешёнными прерываниями для работы SPI Timer2.
Запись через это окно запрещена; параметры и их расположение не изменяются.
Размер каждого нового BIN проверяется при сборке; прежний вариант с одним
PEEK занимал 32 286 байт. Точный размер новой сборки указан в её manifest.
Для реального увеличения памяти потребовались бы другая микросхема, проверка
SPI-загрузчика и драйвера, а не только правка линкера.
## Выбор типа памяти (прошивка 0.1.2)
По умолчанию остаётся AT25256B. Автоматического определения ёмкости нет:
контрольные записи для определения типа не выполняются. Выбранный тип должен
совпадать с физической микросхемой до запуска программы и чтения параметров.
### CCS12
Откройте `Source/Internal/Include/eeprom_config.h`:
```c
#define EEPROM_M95M02 0 /* AT25256B, 32 КиБ */
/* Для установленной M95M02 измените значение на 1. */
```
После изменения выполните **Project → Clean**, затем **Rebuild**. Этот файл
читают драйвер, обработчик команд и F28335.cmd; post-build использует тот же
выбор. Не задавайте противоречащий ему отдельный define только компилятору CCS:
тогда компилятор, линкер и проверка BIN могут получить разные настройки.
### Сборка скриптом
```powershell
./build_logger.ps1 -Eeprom AT25256B -OutputDirectory ./Bin/EepromProfiles/AT25256B -ImageName UKSSTMS320F28335_AT25256B
./build_logger.ps1 -Eeprom M95M02 -OutputDirectory ./Bin/EepromProfiles/M95M02 -ImageName UKSSTMS320F28335_M95M02
```
Параметр скрипта передаётся компилятору, линкеру и проверке размера; исходный
config не меняется. Скрипт по умолчанию собирает AT25256B. Рядом с BIN создаются
manifest и SHA-256; поле `eeprom` в manifest различает аппаратные варианты.
UART остаётся 115200; алгоритм UART и FIFO не изменялся.
| Параметр | AT25256B | M95M02-DR |
|---|---:|---:|
| Ёмкость, байт | 32768 | 262144 |
| Адрес SPI | 2 байта | 3 байта |
| Страница записи, байт | 64 | 256 |
| Начало параметров, байт | 0x7EFC | 0x3FEFC |
| Начало параметров, слова | 0x3F7E | 0x1FF7E |
| Конец параметров, исключая | 0x7FFE | 0x3FFFE |
| Максимальная область BIN | 32508 | 261884 |
Последние два байта не используются. У M95M02 старшие адресные биты передаются
отдельным восьмибитным тактом, затем младшие 16 бит. Адреса и длины драйвера
расширены до 32 бит только в сборке M95M02. Запись делится по физическим страницам;
готовность определяется WIP в регистре состояния. На плате с новым чипом ещё
нужна проверка записи/считывания и холодного SPI boot; тесты на ПК не заменяют её.
### SETGUI
На вкладках **EEPROM 167** и **Загрузка / запуск / прошивка** есть синхронный
чекбокс **M95M02 · 256 КиБ**. Снято — AT25256B · 32 КиБ. Он выбирает протокол
и допустимый диапазон ПК, но не перепрограммирует тип EEPROM работающей платы.
Во время чтения/загрузки переключатель блокируется.
Для полного дампа нажмите «Вся EEPROM»: 32768 либо 262144 байта. Длина блока
остаётся 240 байт. M95M02 требует блочного чтения; автоматический переход на
PEEK запрещён. Неподходящая прошивка должна отклонить запрос, а не вернуть
повторяющиеся первые 32 КиБ как полный дамп. Дамп можно передать кнопкой
«Сравнить с файлом…» напрямую в сравнение BIN.
При загрузке BIN проверяется поле `eeprom`, если оно присутствует в manifest.
Старые файлы без этого поля не позволяют проверить тип. Команда «В SEEPROM»
использует отдельный подкод M95M02; старая прошивка его не исполняет. Операция
может завершиться таймаутом при несовпадении типа; это не успешная запись.
GUI ограничивает отдельную загрузку в RAM существующим пределом 199993 байт:
ёмкость EEPROM 256 КиБ сама по себе не увеличивает буфер загрузки или RAM CPU.
### Замена микросхемы и параметры
Сначала сохраните полный дамп старой EEPROM и настройки платы. После установки
M95M02 запустите её сборку через CCS, запишите соответствующий BIN и сохраните
параметры штатной командой Save. Параметры автоматически не мигрируют с 0x7EFC
на 0x3FEFC. Простое копирование старого полного дампа в начало новой памяти
не переносит настройки в их новую область и оставляет старый вариант прошивки.
### Общий протокол templates
Реализация и границы: `balsam167_eeprom_layout.h`, `balsam167_eeprom_block.h`,
`periph28335.c`. Публичные функции `*_profile` получают 0 (AT25256B) или 1
(M95M02); старые функции и структура reader из четырёх uint32_t сохранены.
Python — тонкий ctypes-порт. Профили ПМ67/2812 и STM этим выбором не изменяются.
Для M95M02 используется частное расширение окна FC03: адрес 0x8000…0xFFFF,
quantity = 0x8000 | (bank << 8) | word_count, bank 0…3, word_count 1…120.
Каждый запрос содержит свой банк 64 КиБ; общего переключаемого банкового
регистра нет. Блок не пересекает границу банка. Это не стандартное количество
регистров Modbus — пользоваться следует общим API templates.
Подкод EXTEND записи: 4 для AT25256B, 0x44 для M95M02. Адрес назначения в словах,
размер в байтах. Обе стороны проверяют границу области прошивки.
Проверки: C-тест полного дампа 256 КиБ с нечётного адреса, все переходы банков,
CRC и выход за границы; Qt-тесты выбора и запрета fallback; host-тест реального
SPI-автомата `python tests/eeprom_profiles/run_test.py`; сборка обоих вариантов TI.
+167
View File
@@ -0,0 +1,167 @@
# 167: версия работающей прошивки, SHA-256 и выпуск
## Что подключено
167 использует общий `templates/c/firmware-info`, а не копию Climate.
Работающий ПМ35 отдаёт 12 регистров версии через RS и служебный CAN.
После сборки на компьютере создаются SHA-256 BIN и JSON-паспорт.
Общая подробная инструкция, включая перенос на TMS320F2812 и STM32:
[BUILD_IDENTITY.md](../../newProject/SETGUI/third_party/templates/c/firmware-info/BUILD_IDENTITY.md).
| Файл проекта | Роль |
|---|---|
| `UKSSTMS320F28335/firmware_version.h` | Редактируемый номер выпуска: FIRMWARE_VERSION_MAJOR/MINOR/PATCH |
| `UKSSTMS320F28335/firmware_identity.json` | Product и target; версия берётся из C-заголовка |
| `Source/Internal/shared_ump.c` | Подключение общей таблицы версии и привязка к UMP ПМ35 |
| `UKSSTMS320F28335/prepare_identity.py` | Генерация идентичности из C-заголовка перед CCS Debug/Release |
| `build_logger.ps1` | Полный CLI build: prepare, compile, link, SCI8, size check, seal |
| `build_bin.bat` | Debug post-build: проверка SCI8 и паспорт конечного BIN |
| `tests/firmware_identity/port_test.c` | Host-проверка привязки версии к реальному порту 167 |
Номер `0.1.0` — начальная версия разработки этого механизма; он не выдаётся за
историческую версию предыдущих BIN. Назначайте release-номер вручную перед выпуском.
## Сборка из PowerShell
Из корня 167:
```powershell
./build_logger.ps1 -OutputDirectory "$PWD/Bin/FirmwareIdentity" `
-ImageName UKSSTMS320F28335
```
По умолчанию применяется `-O2 --opt_for_speed=0`. Результаты:
```text
Bin/FirmwareIdentity/UKSSTMS320F28335.bin
Bin/FirmwareIdentity/UKSSTMS320F28335.bin.manifest.json
Bin/FirmwareIdentity/UKSSTMS320F28335.bin.sha256
Bin/FirmwareIdentity/generated/firmware_identity.h
Bin/FirmwareIdentity/generated/firmware_identity.json
```
Проверка файла перед передачей:
```powershell
python ../newProject/SETGUI/third_party/templates/c/firmware-info/tools/identity.py verify `
--image Bin/FirmwareIdentity/UKSSTMS320F28335.bin `
--manifest Bin/FirmwareIdentity/UKSSTMS320F28335.bin.manifest.json
```
CLI принимает `-TemplatesRoot`, если общий checkout расположен иначе. Все
исходники общего механизма собираются непосредственно из этого checkout.
## Сборка CCS
В `.cproject` добавлены pre-build и include paths `${BuildDirectory}/generated` и
`templates/c/firmware-info/include`. После изменения настроек закройте/откройте
проект в IDE, затем выполните Clean/Rebuild.
Pre-build требует Python 3.10+ и Git в PATH процесса CCS. `Generated` принадлежит
этому проекту: не запускайте Debug/Release одновременно. Для параллельных
сборок используйте CLI с разными output-каталогами.
Штатный Debug post-build создаёт в `Bin`:
```text
UKSSTMS320F28335_<commit>.bin
UKSSTMS320F28335_<commit>.bin.manifest.json
UKSSTMS320F28335_<commit>.bin.sha256
```
Имя BIN исторически содержит HEAD без dirty-суффикса; достоверные признаки dirty
находятся внутри ответа версии и паспорта. Не определяйте чистоту только по имени.
Release-конфигурация получает embedded identity через pre-build, но её старый
проект не имел автоматического BIN post-build. Для готового проверенного BIN с
паспортом используйте описанный CLI либо настройте release-конверсию по общей
инструкции; нельзя запускать Debug `build_bin.bat` на Release и ожидать другой `.out`.
Если CCS пишет `Error ... (ignored)`, сборка не считается успешной: убедитесь,
что последняя проверка и `seal` завершились без ошибок. Старый BIN с таким же
именем может оставаться на диске после неудачной сборки.
## Как прочитать версию
По прямому RS232/RS485: Modbus **03**, адрес начала **4608 (`0x1200`)**, **12 слов**.
Это zero-based адрес в запросе, а не обозначение `4xxxx` в интерфейсе терминала.
Адрес ведомого остаётся текущим `CNTRL_ADDR`. Возвращается стандартный RTU-ответ
с 24 байтами данных и CRC; дополнительного legacy padding в этом окне нет.
| Адрес | Содержимое |
|---|---|
| `0x1200` | Версия формата = 1 |
| `0x1201..0x1203` | Major, minor, patch |
| `0x1204` | Год UTC |
| `0x1205` | Месяц:день, старший:младший октет |
| `0x1206` | Час:минута UTC |
| `0x1207` | Секунда |
| `0x1208..0x120B` | 8 ASCII-символов Build ID, по два на слово |
По служебному CAN ПМ35: запрос UMP функции 03, `0x1200`, 12 слов; ответ состоит
из шести фрагментов. Используется выбранный Mode и существующий token запроса.
CAN ПМ67 не переназначается. Запись функции 06 в окно версии запрещена.
Старый образ может вернуть исключение или неизвестный контракт. Это не следует
заменять «ожидаемой» версией выбранного BIN. После прошивки переподключитесь или
явно обновите чтение: ответ должен поступить с устройства, а не из кэша GUI.
## Что сравнивать после заливки
Сравните SemVer, все поля UTC и Build ID с паспортом именно загруженного BIN.
`+` означает локальные изменения приложения **или templates**; полный Git SHA
и состояние обоих деревьев записаны в JSON. Проверка SHA на ПК подтверждает
целостность файла, но не заменяет проверку программатором записанной памяти.
В контракте нет аппаратного product ID и хеша всей памяти. Подключённый профиль
должен оставаться ПМ35/167, а файл — `product=Balsam-167-PM35`,
`target=TMS320F28335`. Не переносите файл на ПМ67 только по похожему имени.
## Влияние на память и параметры
С проверенными настройками текущий образ занимает **32 286 байт**, предел —
**32 508**, запас **222 байта**. Механизм добавил 116 байт относительно сборки
без него. Это результат конкретной сборки, не обещание для следующих изменений.
Таблица версии — часть `.econst` прошивки. Её не сохраняют в область параметров;
старые настройки не могут подменить версию после обновления. Размер буфера лога
и размещение EEPROM-параметров не менялись. Дата не разбирается на MCU, SHA-256
не вычисляется на MCU. Проверка размера `check_firmware.ps1` остаётся обязательной.
## Проверки и ограничения
Проверка соответствия паспорта **реально встроенной** таблице в собранном SCI8:
```powershell
python tests/firmware_identity/check_build.py Bin/FirmwareIdentity/UKSSTMS320F28335.bin
```
Этот независимый тест также сверяет SHA и размер, проходит boot-блоки и ищет
точное 12-словное представление версии. Он обнаружит старый объект версии или
паспорт другой сборки. Это тест для 167, не транспортный код для GUI.
Host-тест реального порта (GCC должен быть в PATH):
```powershell
$templates = '../newProject/SETGUI/third_party/templates'
gcc -std=c99 -Wall -Wextra -Werror `
"-I$templates/c/firmware-info/include" -IBin/FirmwareIdentity/generated `
"-I$templates/c/set-protocol" "-I$templates/c/set-protocol/ports/tms320f28335" `
tests/firmware_identity/port_test.c Source/Internal/shared_ump.c `
-o Bin/FirmwareIdentity/port_test.exe
./Bin/FirmwareIdentity/port_test.exe
python tests/ump_logger/test_firmware_image.py
python tests/ump_logger/run_test.py
python tests/ump_logger/run_init_test.py
```
Проверены полный TI build 167, формат SCI8, host-тесты протокола и регистратора,
общие тесты identity/SHA, компиляция общего C-кода TI C28x без FPU и ARM Cortex-M4.
Это не аппаратная проверка TMS2812/STM32 и не полноценная сборка другого проекта.
Физическое чтение нового окна версии и проверку после загрузки нужно выполнить
на стенде. В SETGUI на вкладке «Загрузка / запуск / прошивка» доступны
«Прочитать версию с платы» и «Проверить файл»: показаны SemVer, дата UTC,
Build ID и сравнение с выбранным BIN. Для проверки файла держите рядом его
`.bin.manifest.json`; GUI проверяет product/target, размер и SHA-256 и повторяет
проверку перед передачей. BIN без паспорта отмечается как непроверенный старый
образ; неверный паспорт блокирует загрузку. После RESET прочитайте версию снова.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+115
View File
@@ -0,0 +1,115 @@
# Контроль роста ЗПТ при пуске УМП
УМП с адресом 5 (`Mode=5`) контролирует ЗПТ1 — CAN-индекс 305
(регистр ПМ67 306). УМП 6 контролирует ЗПТ2 — CAN-индекс 8
(регистр ПМ67 9). Масштаб исходного слова: 1 единица = 1 В.
## Условия и последовательность
Начальный безопасный код задания соответствует 4 мА по `DAC_min()`.
Рост напряжения считается достаточным при ЗПТ **не меньше 30 В**.
Проверяется расчётное задание ЦАП, а не измеренный выходной ток.
Ручная калибровка не подменяет задание при активной команде пуска
или удерживаемом STOP.
Если очередной шаг рампы требует **строго больше 6 мА**, а свежее
CAN-напряжение меньше 30 В, шаг не передаётся в ЦАП. Вместо него:
1. Снимается START, включается STOP, снимается разрешение RES_OUT_2.
2. Положение рампы обнуляется, передаётся код 4 мА без подмены калибровкой.
3. STOP остаётся включённым **1000 мс от момента его установки**.
4. Когда код 4 мА отправлен, прошла секунда и есть свежее ЗПТ,
снимается STOP, сразу выставляется START, затем разрешение.
5. Новая рампа начинается с нулевого положения с исходными уставками
`DAC_go`/`DAC_stop` (штатные значения могут начинать рост с 6 мА).
Первый START входит в лимит: **всего 5 попыток**, не 5 дополнительных.
После пятой неудачной попытки удерживаются STOP и 4 мА, разрешение снято,
шестой START не выдаётся. Ошибка снимается командой `cUMPstart=0`;
затем можно подать новый пуск. Команда RESET имеет приоритет перед START.
Отпускание RESET с оставшимся START=1 само по себе не перезапускает УМП.
Выходами и рампой управляет один обработчик Timer1 (1 мс). Основной цикл
больше не формирует независимые фронты START/STOP. Расчёт/отправка ЦАП
остались с периодом 5 мс. Импульс START длится до 1000 мс, но защита
или RESET прекращают его раньше.
## Отсутствие CAN
Таймаут по умолчанию **600 мс**, диапазон настройки **1…60000 мс**.
Свежесть подтверждается только получением нужного ЗПТ в полном
технологическом CAN-кадре с соответствующим битом маски. Другое
напряжение, сервисные запросы, команды и произвольные CAN-пакеты
таймаут не сбрасывают. До первого значения напряжение считается неизвестным.
При таймауте работающая рампа удерживает последнее положение и отправленный
код. Попытка не считается неудачной, STOP по одному таймауту не включается.
После свежего ЗПТ рост продолжается; если оно меньше 30 В при задании
больше 6 мА, выполняется защитный STOP. Если обмен исчез во время
сброса, STOP держится и после секунды — до свежего напряжения.
Первый пуск также ждёт свежего значения.
Настройка хранится в технологическом регистре `0x52` платы нагрузки.
Значение 0 в старом EEPROM заменяется на 600 мс при запуске.
Для сохранения изменения используйте штатную команду сохранения параметров.
## Диагностика и эмуляция в GUI
Во вкладке **Логгер УМП** добавлены:
- **Защита ЗПТ: прочитать** — состояние защиты, число попыток, напряжение.
- **Эмуляция: нет CAN** — получение своего ЗПТ игнорируется платой;
после заданного таймаута рампа удерживается.
- **Эмуляция: ЗПТ не растёт** — для проверки защиты используется 0 В,
хотя настоящие кадры продолжают поступать. Реальные напряжения в
журнале не подменяются. Этот режим действительно вызывает STOP/пуски.
- **Задать таймаут** — запись значения в плату с обязательным чтением обратно.
Кнопки эмуляции включаются после проверки сигнатуры прошивки. Их положение
меняется только после подтверждённого чтения обратно. Два режима можно
включать независимо; при отсутствии свежести проверка роста приостановлена.
Эмуляция живёт только в RAM, не сохраняется в EEPROM и выключена после
перезагрузки. GUI показывает активную эмуляцию отдельно от реального ЗПТ.
При онлайн-чтении GUI опрашивает защиту каждые 500 мс и показывает
**«НЕИСПРАВНОСТЬ: ЗПТ не достигло 30 В за 5 попыток»**.
Старые прошивки без блока защиты продолжают обычное онлайн-чтение;
эмуляция для них недоступна.
Технологические регистры только для чтения: `0x7C` — статус,
`0x7D` — число попыток. Оба включены в быстрый CAN-цикл.
Статус: бит 0 — ошибка пяти попыток, бит 1 — нет свежего ЗПТ,
бит 2 — сброс перед повтором, бит 3 — работа рампы,
биты 4/5 — активная эмуляция отсутствия CAN/роста ЗПТ.
Локальный блок RTU и служебного CAN: FC03 `0x1300`, ровно 8 слов:
| Смещение | Значение |
|---|---|
| 0 | Сигнатура `0x5550` |
| 1 | Версия 1 |
| 2 | Эмуляция: бит 0 — нет CAN, бит 1 — нет роста |
| 3 | Таймаут, мс |
| 4 | Статус защиты |
| 5 | Число попыток |
| 6 | Последнее реальное CAN-напряжение, В |
| 7 | Возраст значения, мс; 65535 до первого значения |
FC06 допускает запись только `0x1302` (0…3) и `0x1303` (1…60000).
Диагностический обмен не обновляет возраст технологического напряжения.
## Проверки
`python tests/load_control/run_test.py` компилирует непосредственно
`DAC.c` с подменой GPIO/IRQ. Проверяются выбор ЗПТ, маски CAN, таймаут,
удержание кода, граница 30 В, порядок STOP/START, точная пауза 1000 мс,
лимит пяти пусков, RESET, эмуляция и невозможность подмены 4 мА калибровкой.
GUI: `tests/test_ump_voltage_protection.py` в SETGUI плюс регрессии логгера.
Сборка: `build_logger.ps1 -OutputDirectory Bin/VoltageProtection
-ImageName UKSSTMS320F28335_voltage_protection`. Размер проверяется до
публикации BIN; для AT25256B предел — 32508 байт. В режиме оптимизации
функции разнесены по секциям, чтобы линкер удалял неиспользуемые функции.
Это проверки на компьютере. Физический ток 4 мА, уровни разрешения,
STOP и интервал 1 с нужно проверить на стенде с установленной прошивкой.
+12
View File
@@ -69,6 +69,18 @@
*/
/* Serial EEPROM is NOT CPU-addressable RAM/Flash. Do not add a fake MEMORY
* region at address zero: SPI boot copies its table into the RAM areas below.
* Export its physical layout as absolute byte/word symbols for .map/debugging.
* The final SCI8 image size (including boot headers) is checked after hex2000. */
#include "Source/Internal/Include/eeprom_config.h"
#include "include/balsam167_eeprom_layout.h"
_EEPROM_TOTAL_BYTES = BALSAM167_EEPROM_BYTES;
_EEPROM_FIRMWARE_LIMIT_BYTES = BALSAM167_EEPROM_FIRMWARE_BYTES;
_EEPROM_PARAMS_START_WORD = BALSAM167_EEPROM_PARAMS_START_WORD;
_EEPROM_PARAMS_START_BYTE = BALSAM167_EEPROM_FIRMWARE_BYTES;
_EEPROM_PARAMS_END_BYTE = BALSAM167_EEPROM_PARAMS_END_WORD * 2;
MEMORY
{
PAGE 0: /* Program Memory */
+1 -1
View File
@@ -1,5 +1,5 @@
#include "DSP2833x_Device.h" // DSP281x Headerfile Include File
#include "DSP2833x_Examples.h" // DSP281x Examples Include File
#include "DSP2833x_Examples.h" // DSP281x Examples Include File
#include "DSP2833x_SWPrioritizedIsrLevels.h"
#include "ADC.h"
+233 -69
View File
@@ -21,12 +21,110 @@ unsigned long WAKEpowse;
unsigned long IMPowse;
unsigned int period_dac, time_dac;
/* Âñå âûõîäû ÓÌÏ è ðàìïà ïðèíàäëåæàò îäíîìó îáðàáîò÷èêó Timer1.
* Îñíîâíîé öèêë áîëüøå íå âûäà¸ò START: èíà÷å îí ìîæåò óâèäåòü êîðîòêèé
* ñïàä êîìàíäû, êîòîðûé ïðîïóñòèò ðàñ÷¸ò ÖÀÏ ñ ïåðèîäîì 5 ìñ. */
#define LOAD_IDLE 0U
#define LOAD_RUN 1U
#define LOAD_RELOAD 2U
#define LOAD_FAULT 3U
#define LOAD_MAX_ATTEMPTS 5U
#define LOAD_VOLTAGE_MIN 30U
#define LOAD_PULSE_MS 1000U
static unsigned int load_state, load_attempts, load_inhibit;
static unsigned int load_count_dac, load_count_ramp, load_init_count;
static unsigned int load_zero_sent, load_stop_asserted, load_initialized;
static float load_go, load_end;
/* 16-áèòíûå ïîëÿ îáùèå ñ CAN ISR. Ñíèìîê è óâåëè÷åíèå âîçðàñòà âûïîëíÿþòñÿ
* ïîä êîðîòêîé áëîêèðîâêîé IRQ; îòïðàâêà SPI ïîä áëîêèðîâêîé íå âûïîëíÿåòñÿ. */
static volatile unsigned int load_voltage, load_voltage_age, load_voltage_seen;
static volatile unsigned int load_simulation;
void Load_control_init(void)
{
load_state = LOAD_IDLE; load_attempts = 0; load_inhibit = 0;
load_count_dac = 0; load_count_ramp = 0; load_init_count = 1;
load_zero_sent = 0; load_stop_asserted = 0; load_initialized = 0;
load_go = 0; load_end = 0;
load_voltage = 0; load_voltage_age = 0; load_voltage_seen = 0;
load_simulation = 0;
IMPowse = 0; WAKEpowse = 0; period_dac = 0; time_dac = 0;
}
/* Âîçðàñò ñáðàñûâàåò òîëüêî íóæíîå íàïðÿæåíèå, à íå ëþáîé CAN-ïàêåò.
* ÓÌÏ 5: ÇÏÒ1, èíäåêñ 305; ÓÌÏ 6: ÇÏÒ2, èíäåêñ 8. Ìàñøòàá 1 Â/åä.
* Ìàñêè ñëîòîâ ñîõðàíÿþò àäðåñà äàæå äëÿ ïðîïóùåííûõ ñëîâ êàäðà. */
void Load_voltage_can(unsigned int header, unsigned int a,
unsigned int b, unsigned int c)
{
unsigned int address = header & 0x1FFFU;
unsigned int selected, i, mask = 0x8000U, data[3];
if (Desk != dsk_LOAD || (load_simulation & 1U)) return;
if (Mode == adr_LOA1) selected = 305U;
else if (Mode == adr_LOA2) selected = 8U;
else return;
data[0] = a; data[1] = b; data[2] = c;
for (i = 0; i < 3; i++, address++, mask >>= 1) {
if ((header & mask) && address == selected) {
load_voltage = data[i];
load_voltage_age = 0;
load_voltage_seen = 1;
}
}
}
/* STOP óäåðæèâàåòñÿ âåñü ñáðîñ, âêëþ÷àÿ îòïðàâêó êîäà 4 ìÀ â ÖÀÏ.
* Ïîâòîðíûé ïóñê ðàçðåø¸í òîëüêî ïîñëå ýòîé îòïðàâêè è 1000 ìñ STOP.
* Ýòî ïîäòâåðæäåíèå ïðîãðàììíîé îòïðàâêè, íå èçìåðåíèå ôèçè÷åñêîãî òîêà. */
static void load_stop(unsigned int state)
{
clear_START_DPTB();
set_STOP_DPTB();
clear_RES_OUT_2();
load_stop_asserted = 1;
load_state = state;
load_count_ramp = 0;
load_zero_sent = 0;
IMPowse = LOAD_PULSE_MS;
}
static void load_start(void)
{
/* Óñòàâêè ôèêñèðóþòñÿ äëÿ êàæäîé ïîïûòêè. Âíóòðåííèé íîëü = 4 ìÀ.
* Èñïðàâëåíà îïå÷àòêà ñòàðîãî îãðàíè÷åíèÿ: îãðàíè÷èâàåì êîíåö ðàìïû,
* à íå çàìåíÿåì íà÷àëüíóþ óñòàâêó íà 16 (÷òî äàâàëî ñêà÷îê äî 20 ìÀ). */
load_go = (float)DAC_go - 4.0;
load_end = (float)DAC_stop - 4.0;
if (load_go < 0) load_go = 0;
if (load_go > 5) load_go = 5;
if (load_end > 16) load_end = 16;
if (load_end <= load_go) load_end = load_go + 1;
ump_dac_go10 = (unsigned int)((load_go + 4.0) * 10.0);
ump_dac_stop10 = (unsigned int)((load_end + 4.0) * 10.0);
load_count_ramp = 0;
load_attempts++;
/* STOP îòïóñêàåì íåïîñðåäñòâåííî ïåðåä START, ðàçðåøåíèå — ïîñëå íåãî.
* Çàäàíèå îñòà¸òñÿ 4 ìÀ äî ïåðâîãî øàãà íîâîé ðàìïû. */
clear_STOP_DPTB();
load_stop_asserted = 0;
set_START_DPTB();
set_RES_OUT_2();
IMPowse = LOAD_PULSE_MS;
load_state = LOAD_RUN;
}
void Setup_DAC_time(void)
{
WAKEpowse = 3L * DAC_FREQ +1;
period_dac = READY_FREQ / DAC_FREQ;
time_dac = LOAD_TIME * DAC_FREQ;
/* Ñòàðûå EEPROM èìåþò íîëü â íîâîì ïàðàìåòðå: èñïîëüçóåì 600 ìñ.
* 1..60000 ìñ ìîæíî ìåíÿòü ÷åðåç ðåãèñòð 0x52 è ñîõðàíÿòü øòàòíî. */
if ((unsigned int)LOAD_CAN_TIMEOUT == 0U ||
(unsigned int)LOAD_CAN_TIMEOUT > 60000U) LOAD_CAN_TIMEOUT = 600;
LOAD_STATUS = 0; LOAD_ATTEMPTS = 0;
load_initialized = 1;
}
void dOUT(int x)
@@ -63,7 +161,7 @@ void Init_DAC(void)
toggle_RES_OUT_1();
}
void Anal_output(long vrot, long maxx)
static void anal_output(long vrot, long maxx, int calibration)
{
long out;
static int x;
@@ -72,7 +170,7 @@ void Anal_output(long vrot, long maxx)
x=0;// !x; òùåòíî áûòèå
if(cCalibrDac) out=DAC_cal();
if(calibration && cCalibrDac) out=DAC_cal();
out = out & 0xFFF;
@@ -99,79 +197,145 @@ Log_to_mem(modbus[0x1D]);
}
void Load_runner()
/* Ñîõðàí¸í ïóáëè÷íûé èíòåðôåéñ; çàùèòíûé ñáðîñ âûçûâàåò âíóòðåííþþ
* ôóíêöèþ ñ calibration=0, ÷òîáû ðó÷íàÿ êàëèáðîâêà íå ïîäìåíèëà 4 ìÀ. */
void Anal_output(long vrot, long maxx)
{
static unsigned int count_dac=0, count_load=0, x=1;
static unsigned int preStart=0;
static float dac_go,dac_stop;
float sinus=0;
int kod;
if (maxx > 0) anal_output(vrot, maxx, 1);
}
if(IMPowse) IMPowse--;
if(++count_dac >= period_dac)
{
count_dac=0;
void Load_runner(void)
{
unsigned int saved, commands, voltage, fresh, timeout;
unsigned int start, reset, active = 0;
float sinus = 0;
if(WAKEpowse)
{
--WAKEpowse;
/* ISR ðàáîòàåò 1 êÃö, íî GPIO/ÖÀÏ íåëüçÿ îáñëóæèâàòü äî çàãðóçêè
* ïàðàìåòðîâ. Ýòî òàêæå èñêëþ÷àåò ïðåæíåå äåëåíèå íà time_dac=0. */
if (!load_initialized || !READY || !period_dac || !time_dac) return;
saved = __disable_interrupts();
commands = (unsigned int)Commands;
voltage = (load_simulation & 2U) ? 0U : load_voltage;
timeout = (unsigned int)LOAD_CAN_TIMEOUT;
if (!timeout || timeout > 60000U) timeout = 600U;
/* Äî ïåðâîãî ïàêåòà çíà÷åíèå 0 íå ñ÷èòàåòñÿ ñâåæèì èçìåðåíèåì.
* Íàñûùåíèå âîçðàñòà èñêëþ÷àåò ëîæíîå âîññòàíîâëåíèå ïîñëå ïåðåïîëíåíèÿ. */
fresh = load_voltage_seen && load_voltage_age < timeout;
if (load_voltage_age < 65535U) load_voltage_age++;
__restore_interrupts(saved);
start = commands & 0x80U; reset = commands & 0x40U;
if (IMPowse) IMPowse--;
if( (WAKEpowse == DAC_FREQ/2*6)||
(WAKEpowse == DAC_FREQ/2*5)||
(WAKEpowse == DAC_FREQ/2*4)||
(WAKEpowse == DAC_FREQ/2*3)||
(WAKEpowse == DAC_FREQ/2*2)||
(WAKEpowse == DAC_FREQ/2*1) )
{
Init_DAC(); x=1;
} }
/* Îòñóòñòâèå êîìàíäû ïóñêà — íà÷àëî íîâîãî ñåàíñà, ñáðàñûâàåò îøèáêó.
* RESET âñåãäà ñòàðøå START; îòïóñêàíèå RESET ïðè îñòàâøåìñÿ START
* íå äà¸ò íåîæèäàííîãî ïîâòîðíîãî ïóñêà: ñíà÷àëà òðåáóåòñÿ START=0. */
if (!start) {
if (load_state != LOAD_IDLE) load_stop(LOAD_IDLE);
load_attempts = 0; load_inhibit = 0;
} else if (reset) {
if (load_state != LOAD_FAULT && load_state != LOAD_IDLE)
load_stop(LOAD_IDLE);
load_inhibit = 1;
}
if (reset && !load_stop_asserted) load_stop(LOAD_IDLE);
else
{
if(cUMPstart)
{
if(!preStart)
{
dac_go=DAC_go-4;
dac_stop=DAC_stop-4;
if(dac_go<0) dac_go=0;
if(dac_go>5) dac_go=5;
if(dac_stop>16) dac_go=16;
if(dac_stop<=dac_go) dac_stop=dac_go+1;
/* Äëÿ ëîãà ñîõðàíÿåì ðåàëüíûå îãðàíè÷åííûå óñòàâêè:
* âíóòðåííèå dac_go/stop îòñ÷èòûâàþòñÿ îò 4 ìÀ. */
ump_dac_go10 = (unsigned int)((dac_go + 4.0) * 10.0);
ump_dac_stop10 = (unsigned int)((dac_stop + 4.0) * 10.0);
}
/* Ïîâòîðíûé START äîïóñêàåòñÿ ïîñëå ñáðîñà ÖÀÏ, îêîí÷àíèÿ STOP è
* âîññòàíîâëåíèÿ íóæíîãî CAN-íàïðÿæåíèÿ. Òàéìàóò ñàì íå òðàòèò ïîïûòêó. */
if (start && !reset && !load_inhibit && !WAKEpowse && fresh &&
load_zero_sent && !IMPowse &&
(load_state == LOAD_IDLE || load_state == LOAD_RELOAD)) load_start();
if (load_state == LOAD_RUN && !IMPowse) clear_START_DPTB();
if(count_load > (time_dac/2))count_load++;
if(++count_load > time_dac) count_load = time_dac;
sinus = ((dac_stop - dac_go) / 16.0 * count_load) + (dac_go / 16.0 * time_dac);
// sinus = (10.0 / 16.0 * count_load) + (2.0 / 16.0 * time_dac);
}
else
{ count_load = 0;
sinus=0;
x = (x+1)&0x3FF;
}
/* Ñíèìîê ñòàäèè ðàçãîíà äëÿ ñîïîñòàâëåíèÿ êîìàíäû CAN,
* îæèäàåìîãî òîêà è êîäà, äåéñòâèòåëüíî îòïðàâëåííîãî ÖÀÏ. */
ump_dac_ramp = count_load;
if (++load_count_dac >= period_dac) {
load_count_dac = 0;
if (WAKEpowse) {
--WAKEpowse;
if (WAKEpowse && !(WAKEpowse % (DAC_FREQ / 2))) {
Init_DAC(); load_init_count = 1;
}
} else {
active = load_state == LOAD_RUN;
if (active) {
/* Ïðè òàéìàóòå ñîõðàíÿåì ïîëîæåíèå È îòïðàâëåííîå çàäàíèå,
* íå äåëàåì STOP è íå ïðîäîëæàåì ðàçãîí áåç îáðàòíîé ñâÿçè. */
if (fresh) {
if (load_count_ramp > time_dac / 2) load_count_ramp++;
if (++load_count_ramp > time_dac) load_count_ramp = time_dac;
}
sinus = ((load_end - load_go) / 16.0 * load_count_ramp) +
(load_go / 16.0 * time_dac);
/* Ïðîâåðÿåì ñëåäóþùèé øàã ÄÎ ïåðåäà÷è ÖÀÏ. 6 ìÀ äîïóñòèìû,
* ñòðîãî >6 ìÀ è <30  òðåáóþò íåìåäëåííîãî STOP.
* Ïåðâàÿ ïîïûòêà âõîäèò â ëèìèò 5; øåñòîãî START íå áóäåò. */
if (fresh && voltage < LOAD_VOLTAGE_MIN &&
sinus * 16.0 > 2.0 * time_dac) {
load_stop(load_attempts >= LOAD_MAX_ATTEMPTS ?
LOAD_FAULT : LOAD_RELOAD);
active = 0; sinus = 0;
}
} else {
load_count_ramp = 0;
load_init_count = (load_init_count + 1) & 0x3FFU;
}
/* Âî âðåìÿ çàùèòû è ïóñêà ðó÷íîé êîä êàëèáðîâêè çàïðåù¸í.
* Êàëèáðîâêà ñîõðàíåíà òîëüêî ïðè ñíÿòîì START è ñâîáîäíîì STOP.
* Íóëåâîé êîä çàäàíèÿ çäåñü îçíà÷àåò òîê 4 ìÀ ïî DAC_min(). */
ump_dac_ramp = load_count_ramp;
ump_dac_duration = time_dac;
ump_dac_position = (unsigned int)sinus;
preStart = cUMPstart;
if (active && !fresh) {
/* Óäåðæèâàåì ÖÀÏ. Äàæå cInitDac íå ìåíÿåò òîê â ïàóçå CAN. */
} else if ((!load_init_count || cInitDac) && !load_stop_asserted) {
Init_DAC(); load_init_count = 1; cInitDac = 0;
} else {
anal_output((int)sinus, time_dac,
!start && !load_stop_asserted);
/* A manual calibration frame is not a confirmed 4 mA reload.
* START must wait for a forced baseline frame after it. */
if (!active) load_zero_sent = !(!start && !load_stop_asserted && cCalibrDac);
}
}
}
/* Äèàãíîñòèêà äîñòóïíà îáû÷íûì Modbus/CAN, íåçàâèñèìî îò ëîããåðà:
* áèò 0 — îøèáêà ïÿòè ïóñêîâ; áèò 1 — íåò ñâåæåãî íàïðÿæåíèÿ;
* áèò 2 — ñáðîñ ïåðåä ïîâòîðîì; áèò 3 — ðàáîòà ðàìïû.
* Îøèáêà óäåðæèâàåòñÿ ñî STOP, ïîêà îïåðàòîð íå ñíèìåò START. */
LOAD_STATUS = (load_state == LOAD_FAULT ? 1U : 0U) |
(!fresh ? 2U : 0U) |
(load_state == LOAD_RELOAD ? 4U : 0U) |
(load_state == LOAD_RUN ? 8U : 0U) |
(load_simulation << 4);
LOAD_ATTEMPTS = load_attempts;
}
if(!x || cInitDac)
{
Init_DAC(); x=1; cInitDac=0;
}
else
{
kod = (int)sinus;
Anal_output(kod, time_dac);
} } } }
/* Local versioned block, shared by RTU and service CAN. Probe its signature
* before writes: old firmware returns exception 2 instead of starting tests.
* Simulation is RAM-only, zeroed at boot, and never copied into EEPROM.
* bit 0 ignores ZPT reception (age reaches the configured timeout);
* bit 1 substitutes 0 V only for protection, leaving real logged CAN intact. */
unsigned int Load_protection_prepare(unsigned int function, unsigned int address,
unsigned int value, unsigned int *out, unsigned int *error)
{
unsigned int saved = __disable_interrupts();
*error = 2;
if (Desk == dsk_LOAD && address == 0x1300U && function == 3 && value == 8) {
out[0] = 0x5550U; out[1] = 1;
out[2] = load_simulation; out[3] = (unsigned int)LOAD_CAN_TIMEOUT;
out[4] = (unsigned int)LOAD_STATUS; out[5] = load_attempts;
out[6] = load_voltage; out[7] = load_voltage_seen ? load_voltage_age : 65535U;
*error = 0;
__restore_interrupts(saved);
return 8;
}
if (Desk == dsk_LOAD && function == 6) {
if (address == 0x1302U && value <= 3U) {
load_simulation = value;
*error = 0;
} else if (address == 0x1303U && value >= 1U && value <= 60000U) {
LOAD_CAN_TIMEOUT = value; *error = 0;
} else if (address == 0x1302U || address == 0x1303U) *error = 3;
}
out[0] = address; out[1] = value;
__restore_interrupts(saved);
return 2;
}
+10
View File
@@ -6,3 +6,13 @@ void Setup_DAC_time(void);
/* Ñ÷¸ò÷èê ïóñêîâîãî èìïóëüñà è âûäåðæêè èíèöèàëèçàöèè äîñòóïíû ëîããåðó. */
extern unsigned long IMPowse, WAKEpowse;
extern unsigned int period_dac, time_dac;
/* Call once before IRQ; complete technological CAN packets update voltage. */
void Load_control_init(void);
void Load_voltage_can(unsigned int header, unsigned int a,
unsigned int b, unsigned int c);
/* 0x1300/8 words: signature, version, simulation, timeout, status, attempts,
* voltage, age. Only 0x1302 (simulation) and 0x1303 (timeout) are writable. */
unsigned int Load_protection_prepare(unsigned int function, unsigned int address,
unsigned int value, unsigned int *out, unsigned int *error);
+2 -2
View File
@@ -10,8 +10,8 @@
4876, 5005, 4876, 4847, 1898, 1873, 2535, 2502, // Áîðò 1
// ÓÌÏ UA UC IA IC 20mA O 4mA O 20mA I 4mA I
3820, 3790, 3505, 3380, 1736, 683, 854, 14, // Αξπς 0 1062 1980
3835, 3785, 3522, 3332, 1745, 689, 860, 17, // Αξπς 1 1720 2405
7580, 7540, 2310, 2420, 1736, 683, 854, 14, // Αξπς 0 1062 1980
7580, 7540, 2310, 2420, 1745, 689, 860, 17, // Αξπς 1 1720 2405
// ÁÊÑÑÄ 100 Ohm 150 Ohm
1281, 1272, 0, 0, 622, 2263, 0, 0,
+15
View File
@@ -0,0 +1,15 @@
#ifndef BALSAM167_EEPROM_CONFIG_H
#define BALSAM167_EEPROM_CONFIG_H
/* Build-time checkbox: 0 = AT25256B (32 KiB), 1 = M95M02-DR (256 KiB).
* This must match the soldered part BEFORE startup loads saved parameters.
* CCS: change the value below, then Project > Clean and Rebuild.
* Command-line builds may override EEPROM_M95M02 for that build only.
* A GUI checkbox cannot change SPI ROM boot or startup initialization.
*/
#ifndef EEPROM_M95M02
#define EEPROM_M95M02 0
#endif
#if EEPROM_M95M02 != 0 && EEPROM_M95M02 != 1
#error EEPROM_M95M02_must_be_0_or_1
#endif
#endif
+28
View File
@@ -0,0 +1,28 @@
#ifndef BALSAM167_GAS_H
#define BALSAM167_GAS_H
#include "eeprom_config.h"
#include <stdint.h>
/* Board profile 167/v1, word addresses, independent of the wire transport.
* The original register and UMP addresses remain valid in both builds.
* Only the physically fitted M95M02 version exposes the additional regions. */
#if EEPROM_M95M02
#define GAS167_REG_BASE 0x5000U
#define GAS167_REG_COUNT 128U
#define GAS167_INFO_BASE 0x5F00U
#define GAS167_INFO_WORDS 8U
#define GAS167_MAX_WORDS 124U
#define GAS167_PROFILE_VERSION 1U
#define GAS167_CAPABILITIES 7U /* bit 0 RTU, bit 1 service CAN, bit 2 FC06 */
/* Called by the main-loop transports under their snapshot IRQ lock.
* Capacity: at least GAS167_MAX_WORDS uint16_t words.
* RTU reads: 1..124 words; paired service CAN reads: 2..124, even.
* Successful reads return their count. Writes/errors return address/value.
* error uses Modbus codes 0 (success), 1 (function), 2 (address), 3 (value).
* No EEPROM I/O, allocation, or changes to the existing logger. */
int gas167_contains(uint16_t address);
unsigned gas167_prepare(unsigned function, uint16_t address, uint16_t value,
int can, uint16_t *reply, unsigned *error);
#endif
#endif
+5
View File
@@ -95,6 +95,11 @@
#define DAC_go modbus[0x50] // íà÷àëî çàðàäà
#define DAC_stop modbus[0x51] // êîíåö çàðàäà
/* LOAD-only persistent CAN timeout (ms), read-only status and attempts. */
#define LOAD_CAN_TIMEOUT modbus[0x52]
#define LOAD_STATUS modbus[0x7C]
#define LOAD_ATTEMPTS modbus[0x7D]
#define Cancount (modbus+0x60) // ïàóçà ìåæäó I ïîñûëêàìè CAN
#define Bright (modbus+0x62) // àðêîñòü ñèãíàëüíûõ ëàìïî÷åê
#define Brightness modbus[0x62] // àðêîñòü ñèãíàëüíûõ ëàìïî÷åê
+22 -9
View File
@@ -2,8 +2,19 @@
#define __SPISE2P_H__
/* Physical EEPROM layout shared with the linker and SETGUI. */
#include "eeprom_config.h"
#include "include/balsam167_eeprom_layout.h"
// ¨ìêîñòü ïàìàòè â áàéòàõ
#define SEEPROM_LEN 0x10000
/* Preserve compact 16-bit arithmetic for the original 32-KiB build.
* Only the large part needs 32-bit word addresses and transfer lengths. */
#if EEPROM_M95M02
typedef unsigned long SE2P_SIZE;
#else
typedef unsigned int SE2P_SIZE;
#endif
#define SEEPROM_LEN BALSAM167_EEPROM_BYTES
#define NULL 0
@@ -58,8 +69,10 @@ typedef struct {
/* Object declaration */
typedef struct {
SE2P_DATA *msgPtr;
unsigned int csr; /* control/status register */
/* Main loop publishes the descriptor before setting csr; Timer2 consumes it.
* Both accesses are volatile so optimization cannot publish the request first. */
SE2P_DATA * volatile msgPtr;
volatile unsigned int csr; /* control/status register */
void (*init)(void *);
void (*tick)(void *);
void (*csset)(void);
@@ -85,12 +98,12 @@ void spiSe2pWrite(SPISE2P_DRV *se2p, SE2P_DATA *data);
void spiSe2pRead(SPISE2P_DRV *se2p, SE2P_DATA *data);
#if(SPISE2P_DATA_WIDTH==SIXTEEN_BIT)
#define PROM_LEN 0x8000
#define PAGE_LEN 0x20
#define PROM_LEN BALSAM167_EEPROM_WORDS
#define PAGE_LEN (BALSAM167_EEPROM_PAGE_BYTES / 2)
#define WORD_LEN 2
#else
#define PROM_LEN 0x4000
#define PAGE_LEN 0x40
#define PROM_LEN BALSAM167_EEPROM_BYTES
#define PAGE_LEN BALSAM167_EEPROM_PAGE_BYTES
#define WORD_LEN 1
#endif
@@ -105,7 +118,7 @@ void InitSeeprom(void);
** adres = 0..0x4000, åñëè äëèíà ñëîâà 16 áèò **
** buf - óêàçàòåëü íà ïàìàòü, îòêóäà ïèñàòü. **
** size - äëèíà áëîêà â áàéòàõ. Ïî-ëþáîìó â áàéòàõ! */
void Seeprom_write(unsigned int adres, unsigned int buf[], unsigned int size);
void Seeprom_write(SE2P_SIZE adres, unsigned int buf[], SE2P_SIZE size);
/* ×òåíèå áëîêà èç SEEPROM. Ïàðàìåòðû òàêîâû: **
** adres - àäðåñ â åïðîìêå, îòêóäà ÷èòàòü. **
@@ -113,7 +126,7 @@ void Seeprom_write(unsigned int adres, unsigned int buf[], unsigned int size);
** adres = 0..0x4000, åñëè äëèíà ñëîâà 16 áèò **
** buf - óêàçàòåëü íà ïàìàòü, êóäà ÷èòàòü. **
** size - äëèíà áëîêà â áàéòàõ. Ïî-ëþáîìó â áàéòàõ! */
void Seeprom_read(unsigned int adres, unsigned int buf[], unsigned int size);
void Seeprom_read(SE2P_SIZE adres, unsigned int buf[], SE2P_SIZE size);
#endif
+10 -1
View File
@@ -398,7 +398,16 @@ void extendbios(RS_DATA *rs_arr)
switch ( code )
{
// Ïðîøèâàåì EPROM Èç RAM
case 4: Seeprom_write(Address1,(unsigned int*)Address2,Length);
#if EEPROM_M95M02
/* 0x44 is intentionally rejected by old firmware. Conversely this build
* rejects legacy code 4, preventing writes with the wrong GUI profile. */
case 0x44:
#else
case 4:
#endif
if (!Length || Address1 >= BALSAM167_EEPROM_FIRMWARE_BYTES/2UL ||
Length > BALSAM167_EEPROM_FIRMWARE_BYTES-Address1*2UL) return;
Seeprom_write(Address1,(unsigned int*)Address2,Length);
break;
// ×èòàåì èç EPROM â RAM
case 5: Seeprom_read(Address1,(unsigned int*)Address2,Length);
+19 -4
View File
@@ -12,7 +12,9 @@
#include "message.h"
#include "peripher.h"
#include "log_to_mem.h"
#include "DAC.h"
#include "ump_can_protocol.h"
#include "gas167.h"
// Prototype statements for functions found within this file.
@@ -234,11 +236,18 @@ void Handlai(volatile struct ECAN_MBOXES * ECanMboxes)
Data[1] = (loword>>16) & 0xffff;
Data[2] = (loword ) & 0xffff;
if(bit[0]) if(adr < ANSWER_LEN) modbus[adr] = Data[0]; adr++;
if(bit[1]) if(adr < ANSWER_LEN) modbus[adr] = Data[1]; adr++;
if(bit[2]) if(adr < ANSWER_LEN) modbus[adr] = Data[2];
if(bit[0]) if(adr < ANSWER_LEN &&
!(Desk == dsk_LOAD && (adr == 0x7CU || adr == 0x7DU))) modbus[adr] = Data[0]; adr++;
if(bit[1]) if(adr < ANSWER_LEN &&
!(Desk == dsk_LOAD && (adr == 0x7CU || adr == 0x7DU))) modbus[adr] = Data[1]; adr++;
if(bit[2]) if(adr < ANSWER_LEN &&
!(Desk == dsk_LOAD && (adr == 0x7CU || adr == 0x7DU))) modbus[adr] = Data[2];
/* Ôèêñèðóåì ïðèìåí¸ííóþ êîìàíäó äî âûõîäà èç îáðàáîò÷èêà CAN. */
/* A short frame cannot confirm voltage freshness. Service frames
* UMP_MARKER return above and do not refresh this timeout. */
if (ECanMboxes->MBOX1.MSGCTRL.bit.DLC == 8)
Load_voltage_can((unsigned int)(hiword >> 16), Data[0], Data[1], Data[2]);
ump_log_can((unsigned int)(hiword >> 16), Data[0], Data[1], Data[2]);
led2_toggle();
}
@@ -316,7 +325,13 @@ void UMP_CAN_service(void)
saved = __disable_interrupts();
tag = ump_can_tag; address = ump_can_address; value = ump_can_value;
ump_can_pending = 0;
ump_can_length = ump_can_prepare(tag & 0xFF, address, value, ump_can_reply, &error);
if (address >= 0x1300U && address < 0x1308U)
ump_can_length = Load_protection_prepare(tag & 0xFF, address, value, ump_can_reply, &error);
#if EEPROM_M95M02
else if (gas167_contains(address))
ump_can_length = gas167_prepare(tag & 0xFF, address, value, 1, ump_can_reply, &error);
#endif
else ump_can_length = ump_can_prepare(tag & 0xFF, address, value, ump_can_reply, &error);
ump_can_reply_tag = (tag & 0xFF00) | error;
ump_can_index = 0;
__restore_interrupts(saved);
+63
View File
@@ -0,0 +1,63 @@
#include "gas167.h"
#if EEPROM_M95M02
#include "package.h"
extern int modbus[];
extern int Desk;
extern void ump_log_rs(void);
static const uint16_t gas167_info[GAS167_INFO_WORDS] = {
0x4741U, 0x5331U, GAS167_PROFILE_VERSION, 167U,
GAS167_REG_BASE, GAS167_REG_COUNT, GAS167_CAPABILITIES, 1U
};
int gas167_contains(uint16_t address)
{
return (uint16_t)(address - GAS167_REG_BASE) < GAS167_REG_COUNT ||
(uint16_t)(address - GAS167_INFO_BASE) < GAS167_INFO_WORDS;
}
static unsigned gas167_write(uint16_t offset, uint16_t value)
{
/* Match the existing native Modbus write policy. These status words
* cannot be overwritten and an invalid timeout cannot bypass protection. */
if (Desk == dsk_LOAD && (offset == 0x7CU || offset == 0x7DU)) return 2;
if (Desk == dsk_LOAD && offset == 0x52U &&
(value == 0U || value > 60000U)) return 3;
/* C28x int is one register word; reads always expose the raw uint16_t. */
modbus[offset] = (int)value;
if (offset == 127U) ump_log_rs();
return 0;
}
unsigned gas167_prepare(unsigned function, uint16_t address, uint16_t value,
int can, uint16_t *reply, unsigned *error)
{
uint16_t offset;
unsigned i, available;
int info;
if (!reply || !error) return 0;
*error = 0;
info = (uint16_t)(address - GAS167_INFO_BASE) < GAS167_INFO_WORDS;
offset = (uint16_t)(address - (info ? GAS167_INFO_BASE : GAS167_REG_BASE));
available = info ? GAS167_INFO_WORDS : GAS167_REG_COUNT;
if (function != 3U && function != 6U) *error = 1;
else if (offset >= available) *error = 2;
else if (function == 6U) {
*error = info ? 2U : gas167_write(offset, value);
} else if (!value || value > GAS167_MAX_WORDS || (can && (value & 1U))) {
*error = 3;
} else if (value > available - offset) {
*error = 2;
} else {
/* Validate the entire block before reading. A block cannot cross a
* region boundary, wrap at 0xFFFF, or leak the hidden parameter word. */
for (i = 0; i < value; ++i)
reply[i] = info ? gas167_info[offset+i] : (uint16_t)modbus[offset+i];
return value;
}
reply[0] = address;
reply[1] = value;
return 2;
}
#endif
+28 -31
View File
@@ -31,17 +31,15 @@ void clear_mem()
for (i=Log.Start; i<Log.Finis; i++)
*(volatile int *)i = 0;
}
/* Два банка используют уже выделенную внешнюю RAM .logg (64000 слов).
* Активный банк кольцевой. Через 1000 мс после команды отключения он
* становится архивом, а запись продолжается во втором банке. Переключение
* занимает постоянное время: копирования 30 тысяч слов под запретом IRQ нет.
* Архив переживает разрыв RS-связи, но НЕ исчезновение питания платы. */
/* Two RAM banks: freeze after 10% of bank capacity is recorded after stop. */
static volatile unsigned int ump_state, ump_count, ump_cursor, ump_next;
static volatile unsigned int ump_period = 20, ump_divider;
static volatile unsigned long ump_time, ump_sequence;
static unsigned int ump_bank, ump_archive_bank = 1;
static unsigned int ump_archive_count, ump_archive_next, ump_generation;
static unsigned int ump_source, ump_post, ump_post_dropped;
static unsigned int ump_source, ump_post;
#define UMP_POST_POINTS (UMP_LOG_CAPACITY / 10U)
static void ump_freeze(void);
static unsigned int ump_can_header, ump_can_data[3], ump_can_count;
/* Latest PM67 sums: register 306 (index 305), register 9 (index 8). */
static unsigned int ump_pm67_voltage[2];
@@ -62,7 +60,7 @@ void ump_log_init(void)
ump_time = 0; ump_sequence = 0;
ump_bank = 0; ump_archive_bank = 1;
ump_archive_count = 0; ump_archive_next = 0; ump_generation = 0;
ump_source = 0; ump_post = 0; ump_post_dropped = 0;
ump_source = 0; ump_post = 0;
ump_can_header = 0; ump_can_count = 0;
for (i = 0; i < 3; i++) ump_can_data[i] = 0;
for (i = 0; i < 2; i++) ump_pm67_voltage[i] = 0;
@@ -95,32 +93,30 @@ static unsigned int ump_outputs(void)
static void ump_capture(unsigned int reason)
{
volatile unsigned int *p;
unsigned int i, commands;
unsigned int i, commands, inputs, outputs;
if (ump_state != 1 || Desk != dsk_LOAD || !READY) return;
if (no_write || never_write) { ump_state = 3; return; }
commands = Commands;
/* Отключение: фронт cUMPreset (бит 6) либо спад cUMPstart (бит 7).
* Повторные команды не сдвигают конец уже начатого постинтервала. */
if (!ump_post && (((commands & 0x40) && !(ump_prev_commands & 0x40)) ||
(!(commands & 0x80) && (ump_prev_commands & 0x80)))) {
/* Половину банка резервируем под постинтервал. Даже при периоде
* 1 мс или шквале CAN событие отключения не вытесняется кольцом. */
if (ump_count >= UMP_LOG_CAPACITY/2) ump_count = UMP_LOG_CAPACITY/2-1;
ump_post_dropped = 0;
ump_post = 1000;
inputs = ump_inputs(); outputs = ump_outputs();
/* Freeze only an actual running-to-stopped output transition.
* STOP is set before RES_OUT_2 is cleared in main; an interrupt can
* observe these writes separately, so require its level, not its edge.
* Repeated reset/STOP pulses while already stopped leave the archive. */
if (!ump_post && (ump_prev_outputs & 4U) && !(outputs & 4U) &&
(outputs & 2U)) {
/* Keep 90% including the trigger; reserve 10% for later records. */
if (ump_count >= UMP_LOG_CAPACITY - UMP_POST_POINTS)
ump_count = UMP_LOG_CAPACITY - UMP_POST_POINTS - 1;
ump_post = UMP_POST_POINTS;
reason |= 0x100;
}
/* Последнее место оставляем для снимка конца постинтервала. При
* переполнении честно отмечаем усечение битом 0x200 в конечной записи. */
if (ump_post && ump_count >= UMP_LOG_CAPACITY-1) {
ump_post_dropped = 1;
return;
}
if (reason == 5 && ump_post_dropped) reason |= 0x200;
ump_prev_commands = commands; ump_prev_inputs = inputs; ump_prev_outputs = outputs;
/* Every stored point counts, including CAN and I/O, except the trigger. */
if (ump_post && !(reason & 0x100) && --ump_post == 0) reason = 5;
p = logs_block + ((unsigned long)ump_bank * UMP_LOG_CAPACITY + ump_next) * UMP_LOG_WORDS;
p[0] = (unsigned int)ump_time; p[1] = (unsigned int)(ump_time >> 16);
p[2] = reason; p[3] = commands;
p[4] = ump_inputs(); p[5] = ump_outputs();
p[4] = inputs; p[5] = outputs;
p[6] = (unsigned int)IMPowse; p[7] = (unsigned int)WAKEpowse;
p[8] = modbus[0x7B]; p[9] = modbus[0x1C];
for (i = 0; i < 4; i++) { p[10+i] = sens_data[i]; p[14+i] = modbus[i]; }
@@ -136,11 +132,11 @@ static void ump_capture(unsigned int reason)
p[33] = ump_dac_go10; p[34] = ump_dac_stop10; p[35] = ump_dac_position;
for (i = 0; i < 4; i++) p[36+i] = ADC_table[i];
p[40] = ump_pm67_voltage[0]; p[41] = ump_pm67_voltage[1];
ump_prev_commands = commands; ump_prev_inputs = p[4]; ump_prev_outputs = p[5];
ump_next++;
if (ump_next == UMP_LOG_CAPACITY) ump_next = 0;
if (ump_count < UMP_LOG_CAPACITY) ump_count++;
ump_sequence++;
if (reason == 5) ump_freeze();
}
/* Çàâåðø¸ííûé áàíê áîëüøå íå èçìåíÿåòñÿ äî ñëåäóþùåãî îòêëþ÷åíèÿ.
@@ -160,10 +156,6 @@ void ump_log_tick(void)
if (ump_state == 1) {
ump_time++;
if (++ump_divider >= ump_period) { ump_divider = 0; ump_capture(1); }
if (ump_post && --ump_post == 0) {
ump_capture(5);
ump_freeze();
}
}
__restore_interrupts(saved);
}
@@ -224,6 +216,7 @@ int ump_log_write(unsigned int address, unsigned int value)
ump_count = 0; ump_next = 0; ump_cursor = 0; ump_divider = 0; ump_post = 0;
ump_source = 0;
ump_prev_commands = Commands;
ump_prev_inputs = ump_inputs(); ump_prev_outputs = ump_outputs();
no_write = 0; never_write = 0;
ump_state = 1;
ump_capture(1);
@@ -274,7 +267,11 @@ unsigned int ump_log_read(unsigned int address)
case UMP_LOG_BASE+11: return (unsigned int)READY_FREQ;
case UMP_LOG_BASE+12: return ump_generation;
case UMP_LOG_BASE+13: return ump_archive_count;
case UMP_LOG_BASE+14: return ump_post;
/* Preserve the legacy millisecond status field as an upper estimate.
* Additional event records may finish the post interval sooner. */
case UMP_LOG_BASE+14:
first = (unsigned long)ump_post * ump_period;
return first > 65535UL ? 65535U : (unsigned int)first;
case UMP_LOG_BASE+15: return ump_state;
}
if (address >= UMP_LOG_DATA && address < UMP_LOG_DATA+120 && (ump_source || ump_state != 1)) {
+3 -23
View File
@@ -39,8 +39,6 @@ void main()
{
int i,j,mask;
static unsigned int cancount[2], cancell[2], rscount;
static int preCstart=1,preCstop=1;
int umpstart,umpstop;
RS_DATA * rs;
int presbyt=0;
@@ -83,6 +81,7 @@ void main()
clear_mem();
ump_log_init();
Load_control_init(); /* Explicit initialization before IRQ. */
set_cntrl_addr(Mode,16);
timer_Init();
EnableInterrupts();
@@ -178,27 +177,8 @@ void main()
presbyt = bTermoCal;
}
if(Desk==dsk_LOAD)
{
umpstart = cUMPstart;
umpstop = cUMPreset;
if(umpstart & !preCstart)
{
set_START_DPTB(); set_RES_OUT_2();
IMPowse = READY_FREQ;
}
preCstart = umpstart;
if(umpstop & !preCstop)
{
set_STOP_DPTB(); clear_RES_OUT_2();
IMPowse = READY_FREQ;
}
preCstop = umpstop;
if(!IMPowse) { clear_START_DPTB(); clear_STOP_DPTB(); }
}
/* Timer1 owns START/STOP/permission and the DAC ramp together.
* Main must not re-enable the output during a protected reload. */
/* Ñíèìîê ïîñëå îáíîâëåíèÿ âõîäîâ è ôîðìèðîâàíèÿ ïóñêîâûõ âûõîäîâ. */
ump_log_io();
+1 -1
View File
@@ -417,7 +417,7 @@ void Init_packMask()
if(Desk==dsk_LOAD)
{ Maska[m_SLOW][7]|= 0x0030; // Êàëèáðîâêà ÖÀÏ
Maska[m_SLOW][5]|= 0x0003; // Íà÷àëî è êîíåö çàðàäà
Maska[m_FAST][7]|= 0xE000; // Дублировать команды
Maska[m_FAST][7]|= 0xF000; /* Protection status 0x7C + attempts 0x7D. */ // Äóáëèðîâàòü êîìàíäû
Maska[m_FAST][1]|= 0x3000; // Óïðàâëàþùèé òîê ÑÈÔÓ
} }
+48 -2
View File
@@ -1,3 +1,5 @@
#include "eeprom_config.h"
#include "include/balsam167_eeprom_block.h"
#include "DSP2833x_Device.h" // DSP2833x Headerfile Include File
#include "package.h"
#include "RS485.h"
@@ -18,7 +20,13 @@
#include "caliber.h"
#include "log_to_mem.h"
#include "DAC.h"
#include "ump_tms28335.h"
#include "gas167.h"
#include "include/balsam167_eeprom_layout.h"
#if (ANSWER_LEN + 1) != BALSAM167_EEPROM_PARAM_WORDS
#error EEPROM_parameter_reservation_must_match_ANSWER_LEN
#endif
int modbus[ANSWER_LEN+1];
unsigned int param[ANSWER_LEN+1];
@@ -126,6 +134,7 @@ void Default_params()
// Ïðèøëî âðåìà!
DAC_go = 6; // íà÷àëî çàðàäà
LOAD_CAN_TIMEOUT = 2000; /* ms without fresh ZPT: freeze ramp. */
DAC_stop= 16; // êîíåö çàðàäà
}
@@ -141,7 +150,7 @@ void Load_params()
{
unsigned int i,crc;
Seeprom_read(0x3FFF-(ANSWER_LEN+1), param, (ANSWER_LEN+1)*2);
Seeprom_read(BALSAM167_EEPROM_PARAMS_START_WORD, param, (ANSWER_LEN+1)*2);
crc = get_crc16(param,ANSWER_LEN);
if( (crc==param[ANSWER_LEN]) &&
(crc !=0xFFFF) &&
@@ -174,7 +183,7 @@ void Save_params()
if(dif)
{
param[ANSWER_LEN] = get_crc16(param,ANSWER_LEN);
Seeprom_write(0x3FFF-(ANSWER_LEN+1),param,(ANSWER_LEN+1)*2);
Seeprom_write(BALSAM167_EEPROM_PARAMS_START_WORD,param,(ANSWER_LEN+1)*2);
} }
/***************************************************************/
@@ -206,9 +215,39 @@ static int receive_ump(RS_DATA *rs_arr)
address = (rs_arr->RS_Header[2] << 8) | rs_arr->RS_Header[3];
if (address < UMP_BASE) return 0;
value = (rs_arr->RS_Header[4] << 8) | rs_arr->RS_Header[5];
/* SPI driver advances in Timer2 ISR: NEVER mask interrupts here.
* Reuse the existing snapshot buffer; the main loop owns this transaction.
* The shared validator rejects writes, wraparound and out-of-device reads. */
if (address >= 0x1300U && address < 0x1308U) {
count = Load_protection_prepare(rs_arr->RS_Header[1], address, value, words, &error);
} else if (address >= BALSAM167_EEPROM_RTU_BASE) {
error = 2;
#if EEPROM_M95M02
/* Exception 3 prevents old AT-profile clients from falling back to
* legacy PEEK after a profile mismatch. No SPI transaction is issued. */
if (!(value & BALSAM167_EEPROM_M95_MARKER)) error = 3;
#endif
count = 0;
if (balsam167_eeprom_block_valid(rs_arr->RS_Header[1], address, value)) {
#if EEPROM_M95M02
count = value & 255U;
Seeprom_read(balsam167_eeprom_block_word(address, value), words, count * 2);
#else
count = value;
Seeprom_read(address - BALSAM167_EEPROM_RTU_BASE, words, count * 2);
#endif
error = 0;
}
} else {
saved = __disable_interrupts();
#if EEPROM_M95M02
if (gas167_contains(address))
count = gas167_prepare(rs_arr->RS_Header[1], address, value, 0, words, &error);
else
#endif
count = ump_tms28335_capture(rs_arr->RS_Header[1], address, value, words, &error);
__restore_interrupts(saved);
}
length = ump_rtu_encode(CNTRL_ADDR, rs_arr->RS_Header[1], error,
words, count, rs_arr->buffer, MAX_SEND_LENGTH);
if (length) {
@@ -289,6 +328,13 @@ void ReceiveCommandModbus6(RS_DATA *rs_arr)
// ïîëó÷èëè ñëîâî äàííûõ
Data_MB = (rs_arr->RS_Header[4] << 8) | rs_arr->RS_Header[5];
if (Desk == dsk_LOAD && (Address_MB == 0x7CU || Address_MB == 0x7DU)) {
modbus_exception(rs_arr, CMD_MODBUS_6, 2); return;
}
if (Desk == dsk_LOAD && Address_MB == 0x52U &&
(Data_MB == 0U || Data_MB > 60000U)) {
modbus_exception(rs_arr, CMD_MODBUS_6, 3); return;
}
if (Address_MB < ANSWER_LEN) {
Modbus[Address_MB].all = Data_MB;
if (Address_MB == 127) ump_log_rs();
+4
View File
@@ -1,3 +1,7 @@
/* Build the canonical sources directly; do not copy their implementations. */
#include "../firmware-info/src/firmware_info_static.c"
/* Bind only this PM35 application's identity. The shared transport still owns
* range checks, read-only access, CRC and CAN fragment packing. */
#define UMP_FIRMWARE_INFO_READ firmware_info_static_read
#include "src/ump_protocol.c"
#include "ports/tms320f28335/ump_tms28335.c"
+35 -5
View File
@@ -19,12 +19,18 @@ int ccc=0;
/******* SPI bus Serial EEPROM driver Initialization routine ********/
/********************************************************************/
void Seeprom_write( unsigned int adres,
void Seeprom_write( SE2P_SIZE adres,
unsigned int buf[],
unsigned int size)
SE2P_SIZE size)
{
unsigned int len;
#if EEPROM_M95M02
/* Bound physical accesses before byte-to-word rounding. Legacy 32-KiB
* callers retain their compact driver; EXTEND is checked independently. */
if (adres >= PROM_LEN || !size || size > (PROM_LEN-adres)*2) return;
#endif
CpuTimer2.InterruptCount=0;
CpuTimer2Regs.TCR.all = 0x4020; // Use write-only instruction to set TSS bit = 0
@@ -50,12 +56,18 @@ void Seeprom_write( unsigned int adres,
CpuTimer2Regs.TCR.all = 0x4010; // Use write-only instruction to set TSS bit = 1
}
void Seeprom_read( unsigned int adres,
void Seeprom_read( SE2P_SIZE adres,
unsigned int buf[],
unsigned int size)
SE2P_SIZE size)
{
unsigned int len;
#if EEPROM_M95M02
/* Bound physical accesses before byte-to-word rounding. Legacy 32-KiB
* callers retain their compact driver; EXTEND is checked independently. */
if (adres >= PROM_LEN || !size || size > (PROM_LEN-adres)*2) return;
#endif
CpuTimer2.InterruptCount=0;
CpuTimer2Regs.TCR.all = 0x4020; // Use write-only instruction to set TSS bit = 0
@@ -244,6 +256,15 @@ void SPISE2P_DRV_tick(SPISE2P_DRV *eeprom)
case 5:
/* Send Address */
#if EEPROM_M95M02
SpiaRegs.SPICCR.all=SPISE2P_TFR8BIT;
SpiaRegs.SPITXBUF=(unsigned int)(eeprom->msgPtr->se2pAddr >> 16) << 8;
step=20;
break;
case 20:
if (!SpiaRegs.SPISTS.bit.INT_FLAG) break;
dummy=SpiaRegs.SPIRXBUF;
#endif
#if(SPISE2P_ADDR_WIDTH==SIXTEEN_BIT)
SpiaRegs.SPICCR.all= SPISE2P_TFR16BIT;
SpiaRegs.SPITXBUF=eeprom->msgPtr->se2pAddr;
@@ -374,7 +395,16 @@ void SPISE2P_DRV_tick(SPISE2P_DRV *eeprom)
case 15:
/* Send Address */
#if(SPISE2P_ADDR_WIDTH==SIXTEEN_BIT)
#if EEPROM_M95M02
SpiaRegs.SPICCR.all=SPISE2P_TFR8BIT;
SpiaRegs.SPITXBUF=(unsigned int)(eeprom->msgPtr->se2pAddr >> 16) << 8;
step=21;
break;
case 21:
if (!SpiaRegs.SPISTS.bit.INT_FLAG) break;
dummy=SpiaRegs.SPIRXBUF;
#endif
#if(SPISE2P_ADDR_WIDTH==SIXTEEN_BIT)
SpiaRegs.SPICCR.all= SPISE2P_TFR16BIT;
SpiaRegs.SPITXBUF=eeprom->msgPtr->se2pAddr;
#endif
File diff suppressed because one or more lines are too long
+1
View File
@@ -3,3 +3,4 @@
/.launches/
/targetConfigs/
/project.log
/Release/
+20
View File
@@ -190,6 +190,26 @@
<type>1</type>
<locationURI>SRC_ROOT1/Source/External/v120/DSP2833x_headers/source/DSP2833x_GlobalVariableDefs.c</locationURI>
</link>
<link>
<name>Source/Internal/Include</name>
<type>2</type>
<locationURI>SRC_ROOT1/Source/Internal/Include</locationURI>
</link>
<link>
<name>Source/External/v120/DSP2833x_headers/include</name>
<type>2</type>
<locationURI>SRC_ROOT1/Source/External/v120/DSP2833x_headers/include</locationURI>
</link>
<link>
<name>Source/External/v120/DSP2833x_common/include</name>
<type>2</type>
<locationURI>SRC_ROOT1/Source/External/v120/DSP2833x_common/include</locationURI>
</link>
<link>
<name>Source/Internal/gas167.c</name>
<type>1</type>
<locationURI>SRC_ROOT1/Source/Internal/gas167.c</locationURI>
</link>
</linkedResources>
<variableList>
<variable>
@@ -1,4 +0,0 @@
ccsVersionValidationPolicy=warning
compilerVersionValidationPolicy=flexible
eclipse.preferences.version=1
productVersionsValidationPolicy=flexible
+39
View File
@@ -1,5 +1,40 @@
## Номер выпуска
Меняйте `FIRMWARE_VERSION_MAJOR`, `FIRMWARE_VERSION_MINOR` и
`FIRMWARE_VERSION_PATCH` в `firmware_version.h` в корне проекта CCS.
Например, `0U`, `1U`, `2U` задают версию 0.1.2. Сохраните файл и
выполните Rebuild. После обновления настроек закройте/откройте проект и
выполните Clean/Rebuild.
Debug и Release запускают `prepare_identity.py`: он читает номер из заголовка
и передаёт его общему генератору. `firmware_identity.json` содержит только
product/target; номер выпуска в нём редактировать не нужно.
`${BuildDirectory}/generated/firmware_identity.h` и JSON-паспорт создаются
автоматически; вручную их не меняйте. Диапазоны: major/minor 0..255,
patch 0..999. Неверный номер останавливает генерацию.
# Результаты сборки
Версия 0.1.1 возвращает паспорт прошивки и блочное чтение EEPROM поверх
подтверждённой на плате базы `c6c9128`. COM_1 остаётся на 115200; исходники
`main.c`, `RS485.c`, `RS485.h` и настройки PLL не менялись относительно этой базы.
Не используйте 250000/256000 для проверки этой сборки. FIFO остаётся включённым,
как в рабочей базе; диагностический вариант без FIFO здесь не используется.
Блочное чтение, проверка размера и host-тесты проверяются программно;
после загрузки сначала проверьте чтение версии 0.1.1, затем 256 байт EEPROM.
Debug post-build CCS12 также копирует проверенный BIN и сопутствующие
`.manifest.json`/`.sha256` в
`../newProject/SETGUI/..periph_28335/firmware/periph28335/bal`.
Папка создаётся автоматически; имя BIN с Git commit сохраняется.
Копирование выполняет `copy_bin_to_setgui.ps1` после успешной проверки образа
и создания паспорта. Пути определяются относительно репозитория, не cwd CCS.
Для ручного копирования: `./copy_bin_to_setgui.ps1 -Path ./Bin/<имя>.bin`.
Версия работающей прошивки, SHA-256 BIN и паспорт сборки описаны в
[инструкции для 167](../Doc/FIRMWARE_IDENTITY.md). Перед компиляцией запускается
`preBuildStep.bat`; требуются Python 3.10+ и Git в PATH CCS. Debug post-build
создаёт рядом с BIN файлы `.manifest.json` и `.sha256`.
Для сборки требуется общий UMP-протокол из соседнего репозитория:
`../newProject/SETGUI/third_party/templates/c/set-protocol` (от корня этого
репозитория). Пути к заголовкам заданы в `.cproject` для Debug и Release;
@@ -14,3 +49,7 @@
BIN создаётся скриптом `build_bin.bat`; Git должен быть доступен в `PATH`
процесса CCS. Если определить коммит не удалось, сборка завершается ошибкой.
При наличии незакоммиченных правок хеш обозначает HEAD и не описывает эти правки.
## GAS для новой EEPROM
При EEPROM_M95M02=1 доступен профиль GAS 167/v1 через Modbus и служебный CAN УМП. Карта, проверки и измеренные размеры: [GAS_167_M95M02.md](../Doc/GAS_167_M95M02.md). В сборке AT25256B GAS исключён; её загрузочный BIN не увеличивается. После обновления проекта выполнить Clean/Rebuild: новый gas167.c зарегистрирован в .project.
+4
View File
@@ -0,0 +1,4 @@
{
"product": "Balsam-167-PM35",
"target": "TMS320F28335"
}
+9
View File
@@ -0,0 +1,9 @@
#ifndef BALSAM_FIRMWARE_VERSION_H
#define BALSAM_FIRMWARE_VERSION_H
/* Edit these numbers before releasing firmware, then rebuild. */
#define FIRMWARE_VERSION_MAJOR 1U
#define FIRMWARE_VERSION_MINOR 1U
#define FIRMWARE_VERSION_PATCH 2U
#endif
-33
View File
@@ -1,33 +0,0 @@
@echo off
setlocal
for %%I in ("%~dp0..") do set "SRC_ROOT=%%~fI"
:process_arg
if "%1"=="" goto end_process_arg
set name=%1
set value=
:process_arg_value
if NOT "%value%"=="" set value=%value% %2
if "%value%"=="" set value=%2
shift
if "%2"=="!" goto set_arg
if "%2"=="" goto set_arg
goto process_arg_value
:set_arg
set %name%=%value%
shift
shift
goto process_arg
:end_process_arg
pushd "%SRC_ROOT%\Bin" || exit /b 1
hex2000.exe UKSSTMS320F28335.out -boot -sci8 -map UKSSTMS320F28335.map -o UKSSTMS320F28335.hex -i
set "BUILD_RESULT=%errorlevel%"
popd
if not "%BUILD_RESULT%"=="0" exit /b %BUILD_RESULT%
call "%SRC_ROOT%\build_bin.bat"
exit /b %errorlevel%
+48
View File
@@ -0,0 +1,48 @@
"""Generate build identity using the release version from firmware_version.h."""
import argparse
import importlib.util
import json
from pathlib import Path
import re
import tempfile
def read_version(header):
text = header.read_text(encoding="utf-8-sig")
text = re.sub(r"/\*.*?\*/|//[^\n]*", "", text, flags=re.S)
version = []
for part, limit in (("MAJOR", 255), ("MINOR", 255), ("PATCH", 999)):
name = "FIRMWARE_VERSION_" + part
definitions = re.findall(r"^\s*#\s*define\s+" + name + r"\b([^\n]*)", text, re.M)
if len(definitions) != 1:
raise ValueError(f"{header}: define {name} exactly once")
value = definitions[0].strip()
if not re.fullmatch(r"(?:0|[1-9][0-9]*)[uU]?", value):
raise ValueError(f"{header}: {name} must be a decimal integer")
number = int(value.rstrip("uU"))
if not 0 <= number <= limit:
raise ValueError(f"{header}: {name} must be in 0..{limit}")
version.append(number)
return version
def prepare(project, templates, output):
settings = json.loads((project / "firmware_identity.json").read_text(encoding="utf-8-sig"))
settings["version"] = read_version(project / "firmware_version.h")
spec = importlib.util.spec_from_file_location("shared_firmware_identity", templates / "c/firmware-info/tools/identity.py")
identity = importlib.util.module_from_spec(spec)
spec.loader.exec_module(identity)
# The source header owns SemVer; retain the shared generator and manifest format.
with tempfile.TemporaryDirectory(prefix="balsam-identity-") as directory:
config = Path(directory) / "config.json"
config.write_text(json.dumps(settings), encoding="utf-8")
return identity.prepare(project, templates, config, output)
if __name__ == "__main__":
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--project", type=Path, required=True)
parser.add_argument("--templates", type=Path, required=True)
parser.add_argument("--output", type=Path, required=True)
args = parser.parse_args()
print(json.dumps(prepare(args.project, args.templates, args.output)))
-20
View File
@@ -1,20 +0,0 @@
@echo off
setlocal
set "SRC_ROOT=%~dp0"
set "GIT_COMMIT="
for /f "delims=" %%H in ('git -C "%SRC_ROOT%." rev-parse --short^=8 HEAD') do set "GIT_COMMIT=%%H"
if not defined GIT_COMMIT (
echo ERROR: Cannot determine the Git commit. Make Git available in PATH. 1>&2
exit /b 1
)
pushd "%SRC_ROOT%Bin" || exit /b 1
hex2000.exe UKSSTMS320F28335.out -boot -sci8 -map UKSSTMS320F28335.map -o UKSSTMS320F28335_%GIT_COMMIT%.bin.pending -b
set "BUILD_RESULT=%errorlevel%"
if not "%BUILD_RESULT%"=="0" goto finish
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%SRC_ROOT%check_firmware.ps1" -Path "UKSSTMS320F28335_%GIT_COMMIT%.bin.pending" -PublishPath "UKSSTMS320F28335_%GIT_COMMIT%.bin"
set "BUILD_RESULT=%errorlevel%"
:finish
popd
exit /b %BUILD_RESULT%
-53
View File
@@ -1,53 +0,0 @@
param(
[string]$TemplatesRoot = "$PSScriptRoot/../newProject/SETGUI/third_party/templates",
[string]$CompilerRoot = 'C:/ti/ccs1230/ccs/tools/compiler/ti-cgt-c2000_22.6.0.LTS',
[string]$OutputDirectory = "$PSScriptRoot/Bin/LoggerEepromFix",
[switch]$OptimizeForSize,
[ValidateSet(0,1)][int]$Rs232Fifo = 1,
[string]$ImageName = "UKSSTMS320F28335_logger_eeprom_fix"
)
$ErrorActionPreference = 'Stop'
$protocol = Join-Path (Resolve-Path -LiteralPath $TemplatesRoot).Path 'c/set-protocol'
foreach ($required in @('src/ump_protocol.c', 'ports/tms320f28335/ump_tms28335.c')) {
if (!(Test-Path -LiteralPath (Join-Path $protocol $required))) {
throw "Missing $required in $protocol. Use templates with the UMP v3 port."
}
}
$output = $OutputDirectory
$objects = Join-Path $output 'obj'
New-Item -ItemType Directory -Force -Path $objects | Out-Null
$includes = @('Source/Internal/Include', 'Source/External/v120/DSP2833x_headers/include',
'Source/External/v120/DSP2833x_common/include') | ForEach-Object { "--include_path=$PSScriptRoot/$_" }
$includes += @("--include_path=$CompilerRoot/include", "--include_path=$protocol",
"--include_path=$protocol/ports/tms320f28335")
# Match the CCS Debug configuration used for 34a95bc5.
$flags = @('-v28', '-ml', '-mt', '--float_support=fpu32', '-O1', '--define=LARGE_MODEL',
'--define=_DEBUG', '--symdebug:dwarf', '--abi=coffabi', '--opt_for_speed=3')
$flags += "--define=RS232_USE_FIFO=$Rs232Fifo"
if ($OptimizeForSize) {
$flags = @($flags | Where-Object { $_ -ne '-O1' -and $_ -ne '--opt_for_speed=3' })
$flags += @('-O2', '--opt_for_speed=0')
}
$external = @('DSP2833x_ADC_cal.asm', 'DSP2833x_Adc.c', 'DSP2833x_CpuTimers.c',
'DSP2833x_PieCtrl.c', 'DSP2833x_SWPrioritizedDefaultIsr.c', 'DSP2833x_SWPrioritizedPieVect.c',
'DSP2833x_SysCtrl.c', 'DSP2833x_Xintf.c', 'DSP2833x_usDelay.asm')
$sources = @(Get-ChildItem -LiteralPath "$PSScriptRoot/Source/Internal" -Filter '*.c' | Sort-Object Name | ForEach-Object FullName)
$sources += $external | ForEach-Object { "$PSScriptRoot/Source/External/v120/DSP2833x_common/source/$_" }
$sources += "$PSScriptRoot/Source/External/v120/DSP2833x_headers/source/DSP2833x_GlobalVariableDefs.c"
$objectFiles = @()
foreach ($source in $sources) {
& "$CompilerRoot/bin/cl2000.exe" @flags @includes --compile_only "--obj_directory=$objects" $source
if ($LASTEXITCODE -ne 0) { throw "Compilation failed: $source" }
$objectFiles += Join-Path $objects ([IO.Path]::GetFileNameWithoutExtension($source) + '.obj')
}
$name = $ImageName
& "$CompilerRoot/bin/cl2000.exe" @flags -z "--map_file=$output/$name.map" --stack_size=0x3f0 --warn_sections `
"--search_path=$CompilerRoot/lib" --reread_libs --entry_point=_c_int00 --rom_model `
"--output_file=$output/$name.out" @objectFiles "$PSScriptRoot/F28335.cmd" `
"$PSScriptRoot/Source/External/v120/DSP2833x_headers/cmd/DSP2833x_Headers_nonBIOS.cmd" `
"$PSScriptRoot/Libraries/rts2800_fpu32.lib"
if ($LASTEXITCODE -ne 0) { throw 'Link failed' }
& "$CompilerRoot/bin/hex2000.exe" "$output/$name.out" -boot -sci8 -map "$output/${name}_hex.map" -o "$output/$name.bin.pending" -b
if ($LASTEXITCODE -ne 0) { throw 'Binary conversion failed' }
& "$PSScriptRoot/check_firmware.ps1" -Path "$output/$name.bin.pending" -PublishPath "$output/$name.bin"
Write-Output "Firmware: $output/$name.bin"
-36
View File
@@ -1,36 +0,0 @@
param(
[Parameter(Mandatory=$true)][string]$Path,
[string]$PublishPath
)
$ErrorActionPreference = 'Stop'
# Load_params/Save_params use word address 0x3FFF-(ANSWER_LEN+1).
# Seeprom_write converts that address to wire bytes (WORD_LEN=2).
$header = Get-Content -Raw -LiteralPath "$PSScriptRoot/Source/Internal/Include/message.h"
if ($header -notmatch '(?m)^#define\s+ANSWER_LEN\s+(0x[0-9a-fA-F]+|[0-9]+)') {
throw 'Cannot determine the EEPROM parameter reservation from ANSWER_LEN'
}
$registerCount = if ($Matches[1].StartsWith('0x')) {
[Convert]::ToInt32($Matches[1].Substring(2), 16)
} else { [int]$Matches[1] }
$limit = (0x3FFF - ($registerCount + 1)) * 2
$bytes = [IO.File]::ReadAllBytes((Resolve-Path -LiteralPath $Path).Path)
if ($bytes.Length -gt $limit) {
throw "Firmware size $($bytes.Length) exceeds EEPROM firmware area $limit bytes; overlaps saved parameters by $($bytes.Length - $limit) bytes."
}
if ($bytes.Length -lt 30 -or [BitConverter]::ToUInt16($bytes, 0) -ne 0x08AA) {
throw 'Expected a C2000 SCI8 boot image'
}
$offset = 22
$blocks = 0
while ($true) {
if ($offset + 2 -gt $bytes.Length) { throw 'Missing boot table terminator' }
$words = [BitConverter]::ToUInt16($bytes, $offset)
$offset += 2
if (!$words) { break }
$offset += 4 + 2 * $words
if ($offset -gt $bytes.Length) { throw 'Truncated boot table block' }
$blocks++
}
if (!$blocks -or $offset -ne $bytes.Length) { throw 'Invalid boot table length' }
if ($PublishPath) { Move-Item -LiteralPath $Path -Destination $PublishPath -Force }
Write-Output "Verified SCI8 image: $($bytes.Length) bytes, $blocks blocks, $($limit - $bytes.Length) bytes before saved parameters."
+21
View File
@@ -0,0 +1,21 @@
"""Check SPI wire address widths for both production EEPROM configurations."""
from pathlib import Path
import subprocess
import os
import shutil
import tempfile
root = Path(__file__).resolve().parents[2]
protocol = root.parent / 'newProject/SETGUI/third_party/templates/c/set-protocol'
compiler = os.environ.get('CC') or shutil.which('gcc') or 'C:/msys64/mingw64/bin/gcc.exe'
os.environ['PATH'] = str(Path(compiler).resolve().parent) + os.pathsep + os.environ['PATH']
with tempfile.TemporaryDirectory() as folder:
for profile in (0, 1):
exe = Path(folder) / f'spi{profile}.exe'
subprocess.run([compiler, '-std=c99',
f'-DEEPROM_M95M02={profile}', '-Itests/eeprom_profiles/stubs',
'-ISource/Internal/Include', '-I' + str(protocol),
'tests/eeprom_profiles/spi_test.c', '-o', str(exe)],
cwd=root, check=True)
subprocess.run([str(exe)], check=True, timeout=10)
print(f'PASS SPI profile {profile}')
+47
View File
@@ -0,0 +1,47 @@
/* Exercise the production Timer2 SPI state machine; no hardware writes. */
#include <assert.h>
#include "../../Source/Internal/spise2p.c"
static unsigned trace[300], widths[300], used;
static void transfer(unsigned long address, int write)
{
unsigned buf[2]={0x1234,0x5678}, i;
SE2P_DATA message={buf,2,address};
used=0;
if(write) spiSe2pWrite(&se2p,&message); else spiSe2pRead(&se2p,&message);
for(i=0;i<100 && !spiSe2pFree(&se2p);i++) {
SpiaRegs.SPITXBUF=0xdead;
SpiaRegs.SPIRXBUF=0; /* WIP clear for write, deterministic read data. */
SpiaRegs.SPISTS.bit.INT_FLAG=1;
SPISE2P_DRV_tick(&se2p);
if(SpiaRegs.SPITXBUF!=0xdead) {
assert(GpioDataRegs.GPADAT.bit.GPIO19==0);
trace[used]=SpiaRegs.SPITXBUF;
widths[used++]=(SpiaRegs.SPICCR.all&15)+1;
}
}
assert(spiSe2pFree(&se2p) && GpioDataRegs.GPADAT.bit.GPIO19==1);
i=0;
if(write) { assert(trace[i]==0x0600 && widths[i++]==8); }
assert(trace[i]==(write ? 0x0200 : 0x0300) && widths[i++]==8);
#if EEPROM_M95M02
assert(trace[i]==((address>>16)<<8) && widths[i++]==8);
#endif
assert(trace[i]==(address&65535) && widths[i++]==16);
assert(trace[i]==(write ? 0x1234 : 0) && widths[i++]==16);
assert(trace[i]==(write ? 0x5678 : 0) && widths[i++]==16);
if(write) { assert(trace[i]==0x0500 && widths[i++]==8); assert(trace[i++]==0); }
assert(i==used);
}
int main(void)
{
SPISE2P_DRV_init(&se2p);
transfer(0x7ffc,0); transfer(0x7ffc,1);
#if EEPROM_M95M02
assert(BALSAM167_EEPROM_FIRMWARE_BYTES==261884UL);
transfer(0xfffe,0); transfer(0x10000,0);
transfer(0x20000,1); transfer(0x3fffc,0); transfer(0x3fffc,1);
#else
assert(BALSAM167_EEPROM_FIRMWARE_BYTES==32508);
#endif
return 0;
}
@@ -0,0 +1,22 @@
#ifndef EEPROM_TEST_DEVICE_H
#define EEPROM_TEST_DEVICE_H
#include <stdint.h>
#define interrupt
#define EALLOW
#define EDIS
#define CLKMULT 2
#define SYSCLKOUT 60000000UL
#define M_INT14 0x2000
static unsigned IER;
struct bits { unsigned GPIO16,GPIO17,GPIO18,GPIO19,SPISWRESET,BUFFULL_FLAG,INT_FLAG; };
struct reg { unsigned all; struct bits bit; };
static struct { struct reg GPAMUX2,GPADIR; } GpioCtrlRegs;
static struct { struct reg GPADAT; } GpioDataRegs;
static struct { void (*TINT2)(void); } PieVectTable;
static struct { unsigned InterruptCount; } CpuTimer2;
static struct { struct reg TCR; } CpuTimer2Regs;
static struct { struct reg SPICCR,SPICTL,SPISTS,SPIFFTX,SPIFFRX,SPIFFCT,SPIPRI;
unsigned SPIBRR; uint16_t SPITXBUF,SPIRXBUF; } SpiaRegs;
static void InitCpuTimers(void) {}
static void ConfigCpuTimer(void *p, unsigned long f, unsigned t) {(void)p;(void)f;(void)t;}
#endif
+42
View File
@@ -0,0 +1,42 @@
"""Independent test oracle: does this built SCI8 file contain its manifest identity?
This is a build test, not a second production protocol codec. The 12-word vector
is deliberately constructed independently of the C implementation to detect a
stale compilation unit, incorrect byte order or a mismatched pre-build JSON.
Usage: python tests/firmware_identity/check_build.py path/to/firmware.bin
"""
from datetime import datetime
import hashlib
import json
from pathlib import Path
import struct
import sys
image = Path(sys.argv[1])
data = image.read_bytes()
meta = json.loads(image.with_suffix(image.suffix + '.manifest.json').read_text())
assert hashlib.sha256(data).hexdigest() == meta['sha256'], 'Wrong file hash'
assert len(data) == meta['size_bytes'], 'Wrong image size'
stamp = datetime.fromisoformat(meta['built_at_utc'])
build_id = meta['build_id']
words = [1, *meta['version'], stamp.year, stamp.month * 256 + stamp.day,
stamp.hour * 256 + stamp.minute, stamp.second]
words += [ord(build_id[i]) * 256 + ord(build_id[i+1]) for i in range(0, 8, 2)]
expected = struct.pack('<12H', *words)
assert data[:2] == b'\xaa\x08', 'Not SCI8'
offset = 22
found = 0
while True:
count, = struct.unpack_from('<H', data, offset)
offset += 2
if not count:
break
offset += 4 # destination address, not part of the loaded section payload
end = offset + count * 2
assert end <= len(data), 'Truncated boot block'
block = data[offset:end]
found += sum(block[i:i+24] == expected for i in range(0, len(block)-23, 2))
offset = end
assert offset == len(data), 'Trailing data'
assert found == 1, f'Expected one embedded identity, found {found}'
print(f'PASS: SCI8 contains manifest identity {meta["version"]}, {build_id}, {stamp.isoformat()}')
+33
View File
@@ -0,0 +1,33 @@
/* Test the actual 167 binding, not just a fake protocol backend. Build against
* the generated header from the firmware build under test. No MCU registers. */
#include "firmware_identity.h"
#include "firmware_info_static.h"
#include "ump_tms28335.h"
#include <assert.h>
static unsigned logger_reads, logger_writes;
unsigned int ump_log_read(unsigned int address)
{ (void)address; ++logger_reads; return 0x554dU; }
int ump_log_write(unsigned int address, unsigned int value)
{ (void)address; (void)value; ++logger_writes; return 1; }
int main(void)
{
uint16_t words[UMP_MAX_WORDS];
ump_octet wire[UMP_RTU_CAPACITY];
unsigned error, i;
assert(ump_tms28335_prepare(3, UMP_FIRMWARE_INFO, 12, words, &error) == 12 && !error);
assert(words[0] == 1 && words[1] == FIRMWARE_VERSION_MAJOR);
assert(words[2] == FIRMWARE_VERSION_MINOR && words[3] == FIRMWARE_VERSION_PATCH);
assert(words[4] == FI_YEAR && words[5] == FI_PAIR(FI_MONTH, FI_DAY));
assert(words[8] == FI_PAIR(FI_ID_0, FI_ID_1));
assert(logger_reads == 0);
assert(ump_tms28335_rtu(1,3,UMP_FIRMWARE_INFO,12,wire,UMP_RTU_CAPACITY) == 29);
for (i=0; i<12; ++i) assert(((wire[3+i*2]<<8) | wire[4+i*2]) == words[i]);
assert(ump_crc(wire,29) == 0);
ump_tms28335_prepare(6,UMP_FIRMWARE_INFO,123,words,&error);
assert(error == 2 && logger_writes == 0);
/* Firmware reads must not steal the existing logger window. */
assert(ump_tms28335_prepare(3,UMP_BASE,16,words,&error) == 16 && !error);
assert(logger_reads == 16 && words[0] == 0x554dU);
return 0;
}
+48
View File
@@ -0,0 +1,48 @@
"""Validate the PM35 RAM-load SCI8 image against the selected EEPROM profile."""
import argparse
from pathlib import Path
import struct
def check_image(path, eeprom):
data = path.read_bytes()
limit = {"AT25256B": 32508, "M95M02": 261884}[eeprom]
if not 24 <= len(data) <= limit:
raise ValueError(f"Image size {len(data)} outside 24..{limit}")
if struct.unpack_from("<H", data)[0] != 0x08AA:
raise ValueError("Not a SCI8 boot image")
high, low = struct.unpack_from("<HH", data, 18)
entry = (high << 16) | low
position, blocks = 22, []
while True:
if position + 2 > len(data):
raise ValueError("Missing boot terminator")
words = struct.unpack_from("<H", data, position)[0]
position += 2
if not words:
if position != len(data):
raise ValueError("Trailing bytes after boot terminator")
break
if position + 4 + words * 2 > len(data):
raise ValueError("Truncated boot block")
high, low = struct.unpack_from("<HH", data, position)
address = (high << 16) | low
end = address + words
if not 0x8000 <= address < end <= 0xD000:
raise ValueError(f"Boot destination {address:#x}..{end:#x} outside code RAM")
if any(address < old_end and old_start < end for old_start, old_end in blocks):
raise ValueError("Overlapping boot blocks")
blocks.append((address, end))
position += 4 + words * 2
if not any(start <= entry < end for start, end in blocks):
raise ValueError("Entry point not loaded by the boot table")
return len(data), len(blocks), limit - len(data)
if __name__ == "__main__":
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("image", type=Path)
parser.add_argument("--eeprom", choices=("AT25256B", "M95M02"), required=True)
args = parser.parse_args()
size, blocks, free = check_image(args.image, args.eeprom)
print(f"PASS SCI8 {args.eeprom}: {size} bytes, {blocks} RAM blocks, {free} bytes free")
+196
View File
@@ -0,0 +1,196 @@
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include "gas167.h"
#include "ump_protocol.h"
#include "ump_tms28335.h"
#include "include/balsam167_eeprom_block.h"
#define ANSWER_LEN 128U
#define MAX_SEND_LENGTH 400U
#define CNTRL_ADDR 16U
#define CMD_MODBUS_3 3U
#define CMD_MODBUS_6 6U
#define dsk_LOAD 6
#define LOBYTE(v) ((v) & 255U)
#define HIBYTE(v) (((v) >> 8) & 255U)
#define UMP_CAN_MAX_WORDS UMP_MAX_WORDS
#define ump_can_prepare ump_tms28335_prepare
typedef struct { int all; } WORDE;
#define Modbus ((WORDE *)modbus)
typedef struct {
uint16_t RS_Header[8], buffer[MAX_SEND_LENGTH];
unsigned flag_TIMEOUT_to_Send;
} RS_DATA;
int modbus[129], Desk = dsk_LOAD;
static unsigned captures, spi_reads, sent, irq_depth, legacy_reads;
static unsigned __disable_interrupts(void) {
unsigned saved = irq_depth;
++irq_depth;
return saved;
}
static void __restore_interrupts(unsigned saved) { irq_depth = saved; }
void ump_log_rs(void) { ++captures; }
unsigned int ump_log_read(unsigned int address) { ++legacy_reads; return address; }
int ump_log_write(unsigned int address, unsigned int value) { return 1; }
static unsigned get_crc_16(unsigned seed, const uint16_t *data, unsigned size) {
assert(seed == 0xFFFFU);
return ump_crc(data, size);
}
static void RS_Send(RS_DATA *rs, uint16_t *data, unsigned size) {
assert(irq_depth == 0);
sent = size;
}
static void Seeprom_read(unsigned long word, uint16_t *data, unsigned bytes) {
unsigned i;
assert(irq_depth == 0); /* The Timer2 SPI driver must retain interrupts. */
++spi_reads;
for (i = 0; i < bytes/2; ++i) data[i] = (uint16_t)(0xBEEFU+i);
}
static unsigned Load_protection_prepare(unsigned fn, unsigned a, unsigned n,
uint16_t *out, unsigned *error) {
out[0] = (uint16_t)a; out[1] = (uint16_t)n; *error = 0;
return 2;
}
struct mock_reg { uint32_t all; };
static struct {
struct mock_reg CANTA, CANAA, CANTRR, CANTRS;
} ECanaRegs;
static struct {
struct { struct mock_reg MDH, MDL; } MBOX2;
} ECanaMboxes;
static struct { unsigned long InterruptCount; } CpuTimer1;
#include "production_routes.inc"
static void request(RS_DATA *rs, unsigned fn, unsigned a, unsigned n) {
unsigned crc;
memset(rs, 0, sizeof(*rs));
rs->RS_Header[0] = CNTRL_ADDR; rs->RS_Header[1] = fn;
rs->RS_Header[2] = a >> 8; rs->RS_Header[3] = a & 255U;
rs->RS_Header[4] = n >> 8; rs->RS_Header[5] = n & 255U;
crc = ump_crc(rs->RS_Header, 6);
rs->RS_Header[6] = crc & 255U; rs->RS_Header[7] = crc >> 8;
sent = 0;
if (fn == 3) ReceiveCommandModbus3(rs);
else ReceiveCommandModbus6(rs);
assert(irq_depth == 0 && rs->flag_TIMEOUT_to_Send);
assert(ump_crc(rs->buffer, rs->buffer[1] & 128U ? 5 :
(fn == 6 ? 8 : 5+rs->buffer[2])) == 0);
}
static void can_request(unsigned fn, unsigned a, unsigned n) {
memset(&ECanaRegs, 0, sizeof(ECanaRegs));
memset(&ECanaMboxes, 0, sizeof(ECanaMboxes));
ump_can_waiting = 0; ump_can_length = 0; ump_can_index = 0;
ump_can_pending = 1; ump_can_tag = 0x0100U | fn;
ump_can_address = a; ump_can_value = n;
UMP_CAN_service();
assert(irq_depth == 0 && !ump_can_pending);
assert(ECanaRegs.CANTRS.all == 4U);
}
int main(void) {
RS_DATA rs;
unsigned i;
for (i = 0; i < 129; ++i) modbus[i] = (int)i;
modbus[128] = 0xD00D;
/* Exercise the real native handlers in both builds. */
request(&rs, 6, 1, 0xFEDC);
assert(modbus[1] == 0xFEDC && sent == 10);
request(&rs, 3, 0, 2);
assert(rs.buffer[2] == 4 && rs.buffer[5] == 0xFE && rs.buffer[6] == 0xDC);
request(&rs, 6, 0x52, 0);
assert(rs.buffer[1] == 0x86 && rs.buffer[2] == 3 && modbus[0x52] == 0x52);
request(&rs, 6, 0x7C, 9);
assert(rs.buffer[2] == 2 && modbus[0x7C] == 0x7C);
/* Existing UMP and protection routing must still work. */
request(&rs, 3, 0x1000, 2);
assert(rs.buffer[1] == 3 && rs.buffer[3] == 0x10 && legacy_reads == 2);
request(&rs, 3, 0x1300, 2);
assert(rs.buffer[1] == 3 && rs.buffer[3] == 0x13);
#if EEPROM_M95M02
{
uint16_t out[124];
unsigned error, count;
for (i = 0; i < 65536U; ++i)
assert(gas167_contains((uint16_t)i) ==
((i >= 0x5000U && i <= 0x507FU) || (i >= 0x5F00U && i <= 0x5F07U)));
assert(gas167_prepare(3, 0x5000, 124, 0, out, &error) == 124 && !error);
assert(out[1] == 0xFEDC && out[123] == 123);
count = gas167_prepare(3, 0x507F, 2, 0, out, &error);
assert(count == 2 && error == 2 && modbus[128] == 0xD00D);
assert(gas167_prepare(3, 0x5000, 0, 0, out, &error) == 2 && error == 3);
assert(gas167_prepare(3, 0x5000, 125, 0, out, &error) == 2 && error == 3);
assert(gas167_prepare(3, 0x5000, 1, 1, out, &error) == 2 && error == 3);
assert(gas167_prepare(3, 0xFFFF, 2, 0, out, &error) == 2 && error == 2);
assert(gas167_prepare(16, 0x5000, 1, 0, out, &error) == 2 && error == 1);
assert(gas167_prepare(6, 0x5080, 7, 0, out, &error) == 2 && error == 2);
assert(gas167_prepare(6, 0x5052, 60001, 0, out, &error) == 2 && error == 3);
assert(gas167_prepare(6, 0x5052, 60000, 0, out, &error) == 2 && !error);
assert(modbus[0x52] == 60000);
assert(gas167_prepare(3, 0x5000, 1, 0, 0, &error) == 0);
assert(gas167_prepare(6, 0x5000, 1, 0, out, 0) == 0 && modbus[0] == 0);
}
request(&rs, 3, 0x5000, 2);
assert(sent == 9 && rs.buffer[5] == 0xFE && rs.buffer[6] == 0xDC);
request(&rs, 3, 0x5000, 124);
assert(sent == 253 && rs.buffer[2] == 248);
request(&rs, 3, 0x5F00, 8);
assert(sent == 21 && rs.buffer[3] == 0x47 && rs.buffer[4] == 0x41);
assert(rs.buffer[5] == 0x53 && rs.buffer[6] == 0x31);
assert(rs.buffer[10] == 167 && rs.buffer[18] == 1);
request(&rs, 6, 0x5F00, 0);
assert(sent == 5 && rs.buffer[2] == 2);
request(&rs, 3, 0x5F07, 2);
assert(rs.buffer[2] == 2);
request(&rs, 6, 0x5052, 0);
assert(rs.buffer[2] == 3 && modbus[0x52] == 60000);
request(&rs, 6, 0x507D, 9);
assert(rs.buffer[2] == 2 && modbus[0x7D] == 0x7D);
request(&rs, 6, 0x507F, 0x80);
assert(sent == 8 && rs.buffer[2] == 0x50 && rs.buffer[3] == 0x7F);
assert(captures == 1 && modbus[127] == 0x80);
can_request(6, 0x5001, 0xFEDC);
assert(modbus[1] == 0xFEDC && ECanaMboxes.MBOX2.MDL.all == 0x5001FEDCUL);
assert(ECanaMboxes.MBOX2.MDH.all == 0x1F000100UL);
can_request(3, 0x5001, 2);
assert(ECanaMboxes.MBOX2.MDL.all == 0xFEDC0002UL);
can_request(3, 0x5000, 124);
assert(ump_can_length == 124 && ump_can_index == 2);
modbus[2] = 777; /* All fragments must retain the original snapshot. */
for (i = 1; i < 62; ++i) {
ECanaRegs.CANTA.all = 4;
UMP_CAN_service();
assert(ump_can_index == 2*(i+1));
if (i == 1) assert(ECanaMboxes.MBOX2.MDL.all == 0x00020003UL);
}
modbus[2] = 2;
can_request(3, 0x5000, 3);
assert(ECanaMboxes.MBOX2.MDH.all == 0x1F000103UL);
can_request(6, 0x507C, 9);
assert((ECanaMboxes.MBOX2.MDH.all & 255U) == 2 && modbus[0x7C] == 0x7C);
can_request(6, 0x507F, 0x40);
assert(captures == 2 && modbus[127] == 0x40);
can_request(6, 0x5F00, 9);
assert((ECanaMboxes.MBOX2.MDH.all & 255U) == 2);
request(&rs, 3, 0x8000, 0x8001);
#else
request(&rs, 3, 0x5000, 2);
assert(sent == 5 && rs.buffer[2] == 2);
request(&rs, 6, 0x5001, 9);
assert(rs.buffer[2] == 2 && modbus[1] == 0xFEDC);
request(&rs, 3, 0x5000, 124);
assert(sent == 5 && rs.buffer[2] == 2);
request(&rs, 3, 0x5F00, 8);
assert(rs.buffer[2] == 2);
can_request(6, 0x5001, 9);
assert((ECanaMboxes.MBOX2.MDH.all & 255U) == 2 && modbus[1] == 0xFEDC);
assert(captures == 0);
request(&rs, 3, 0x8000, 1);
#endif
assert(spi_reads == 1 && rs.buffer[3] == 0xBE && rs.buffer[4] == 0xEF);
assert(modbus[128] == 0xD00D);
printf("PASS GAS167 profile %d: production RTU/CAN routes, guards, CRC and legacy services\n", EEPROM_M95M02);
return 0;
}
+51
View File
@@ -0,0 +1,51 @@
"""Exercise the production GAS provider and extracted production RTU/CAN routes."""
import os
from pathlib import Path
import re
import shutil
import subprocess
import tempfile
root = Path(__file__).resolve().parents[2]
templates = Path(os.environ.get("TEMPLATES_ROOT", root.parent / "newProject/SETGUI/third_party/templates"))
protocol = templates / "c/set-protocol"
compiler = os.environ.get("CC") or shutil.which("gcc") or "C:/msys64/mingw64/bin/gcc.exe"
os.environ["PATH"] = str(Path(compiler).resolve().parent) + os.pathsep + os.environ["PATH"]
def function(text, name):
match = re.search(r"^(?:static )?(?:void|int) " + name + r"\(", text, re.M)
if not match:
raise ValueError(f"Production function {name} missing")
end = re.search(r"^}", text[match.start():], re.M)
if not end:
raise ValueError(f"Production function {name} incomplete")
return text[match.start():match.start() + end.end()] + "\n"
message = (root / "Source/Internal/message.c").read_text(encoding="cp1251")
ecan = (root / "Source/Internal/ecan.c").read_text(encoding="cp1251")
can_globals = ecan[ecan.index("static volatile unsigned int ump_can_pending;"):
ecan.index("static unsigned long ump_can_sent_at;") + len("static unsigned long ump_can_sent_at;")]
# On C28x unsigned int is uint16_t. Preserve that target word layout on GCC.
can_globals = can_globals.replace("static unsigned int ump_can_reply[",
"static uint16_t ump_can_reply[")
routes = can_globals + "\n" + "".join(function(message, name) for name in
("modbus_exception", "receive_ump", "ReceiveCommandModbus3", "ReceiveCommandModbus6"))
routes += function(ecan, "UMP_CAN_service")
with tempfile.TemporaryDirectory(prefix="gas167-test-") as tmp:
directory = Path(tmp)
(directory / "production_routes.inc").write_text(routes, encoding="utf-8")
for profile in (0, 1):
exe = directory / f"gas167_{profile}.exe"
subprocess.run([compiler, "-std=c99", "-Wall", "-Wextra",
"-Wno-unused-function", "-Wno-unused-parameter",
f"-DEEPROM_M95M02={profile}", "-I" + str(directory),
"-ISource/Internal/Include", "-I" + str(protocol),
"-I" + str(protocol / "include"),
"-I" + str(protocol / "ports/tms320f28335"),
"tests/gas167/gas167_test.c", "Source/Internal/gas167.c",
str(protocol / "src/ump_protocol.c"),
str(protocol / "ports/tms320f28335/ump_tms28335.c"),
"-o", str(exe)], cwd=root, check=True)
subprocess.run([str(exe)], check=True, timeout=10)
+103
View File
@@ -0,0 +1,103 @@
#include <stdio.h>
#include <string.h>
#include "../../Source/Internal/DAC.c"
int READY = 1, Desk = 6, Mode = 5;
int modbus[128];
unsigned int test_start, test_stop, test_permission, test_tick;
unsigned int test_starts, test_last_stop_tick, test_last_start_tick, test_init_dac;
int test_bad_order;
unsigned int ump_dac_ramp, ump_dac_duration, ump_dac_go10, ump_dac_stop10, ump_dac_position;
#define CHECK(x) do { if (!(x)) { printf("FAIL line %d: %s\n", __LINE__, #x); return 1; } } while (0)
static void setup(int mode) {
memset(modbus, 0, sizeof(modbus)); Mode = mode; READY = 1;
test_start = test_stop = test_permission = test_tick = test_starts = 0;
test_bad_order = test_init_dac = 0;
Load_control_init(); Setup_DAC_time(); WAKEpowse = 0;
DAC_go = 4; DAC_stop = 16; DAC_min() = 100; DAC_max() = 3000;
}
static void tick(unsigned int n, int exchange, unsigned int volts) {
while (n--) {
if (exchange) Load_voltage_can(0x8000U | (Mode == 5 ? 305U : 8U), volts, 0, 0);
test_tick++; Load_runner();
}
}
int main(void) {
unsigned int out[8], error, count, ramp, code, stopped_at;
setup(5);
/* Boot ISR before parameters: no divide-by-zero and no GPIO start. */
Load_control_init(); Commands = 0x80; tick(10, 1, 0); CHECK(!test_starts);
Setup_DAC_time(); WAKEpowse = 0;
tick(10, 0, 0); CHECK(test_starts == 1 && test_permission);
CHECK(LOAD_CAN_TIMEOUT == 600);
/* Wrong ZPT and masked slots never confirm freshness. */
setup(5); Commands = 0x80;
Load_voltage_can(0x8000U | 8U, 200, 0, 0);
Load_voltage_can(305U, 200, 0, 0); tick(10, 0, 0);
CHECK(!test_starts && (LOAD_STATUS & 2));
Load_voltage_can(0x2000U | 303U, 0, 0, 30);
tick(1, 0, 0); CHECK(test_starts == 1);
/* Freeze current and code at timeout; unrelated exchange cannot unfreeze. */
tick(1000, 1, 30); tick(600, 0, 0);
ramp = ump_dac_ramp; code = modbus[123];
tick(2000, 0, 0); CHECK(ump_dac_ramp == ramp && (unsigned int)modbus[123] == code);
CHECK(test_permission && !test_stop && (LOAD_STATUS & 2));
Load_voltage_can(0x8000U | 8U, 200, 0, 0); tick(10, 0, 0);
CHECK(ump_dac_ramp == ramp);
tick(10, 1, 30); CHECK(ump_dac_ramp > ramp);
/* Exactly 30 V permits growth; 29 V fails strictly above 6 mA. */
setup(6); Commands = 0x80; tick(10, 1, 30);
load_count_ramp = time_dac / 6; tick(5, 1, 30);
CHECK(load_state == LOAD_RUN && modbus[0x1C] >= 60);
tick(5, 1, 29); CHECK(load_state == LOAD_RELOAD && test_stop && !test_permission);
CHECK(!test_start && ump_dac_ramp == 0 && modbus[0x1C] == 40);
CHECK(modbus[123] == DAC_min()); stopped_at = test_last_stop_tick;
tick(999, 1, 0); CHECK(test_starts == 1 && test_stop);
tick(1, 1, 0); CHECK(test_starts == 2 && test_last_start_tick-stopped_at == 1000);
CHECK(!test_stop && test_permission && !test_bad_order);
/* Five total attempts, then a persistent fault; no sixth start. */
while (load_state != LOAD_FAULT) {
load_count_ramp = time_dac / 6; tick(5, 1, 0);
if (load_state == LOAD_RELOAD) tick(1000, 1, 0);
}
CHECK(test_starts == 5 && LOAD_ATTEMPTS == 5 && (LOAD_STATUS & 1));
tick(3000, 1, 200); CHECK(test_starts == 5 && test_stop && !test_permission);
Commands = 0xC0; tick(10, 1, 200); CHECK(load_state == LOAD_FAULT);
Commands = 0; tick(1000, 1, 200); CHECK(!(LOAD_STATUS & 1));
Commands = 0x80; test_starts = 0; tick(10, 1, 200); CHECK(LOAD_ATTEMPTS == 1);
/* Manual reset dominates START; releasing it alone cannot start again. */
Commands = 0xC0; tick(1, 1, 200); CHECK(test_stop && !test_permission);
Commands = 0x80; tick(1500, 1, 200); CHECK(test_starts == 1);
/* Versioned simulation writes are bounded, read-only fields reject writes. */
count = Load_protection_prepare(3, 0x1300, 8, out, &error);
CHECK(count == 8 && !error && out[0] == 0x5550 && out[1] == 1);
Load_protection_prepare(6, 0x1303, 0, out, &error); CHECK(error == 3);
Load_protection_prepare(6, 0x1304, 0, out, &error); CHECK(error == 2);
Load_protection_prepare(6, 0x1302, 4, out, &error); CHECK(error == 3);
setup(5); Commands = 0x80; tick(10, 1, 200);
Load_protection_prepare(6, 0x1303, 20, out, &error); CHECK(!error);
Load_protection_prepare(6, 0x1302, 1, out, &error); CHECK(!error);
tick(30, 1, 200); CHECK(LOAD_STATUS & 2);
ramp = ump_dac_ramp; tick(100, 1, 200); CHECK(ump_dac_ramp == ramp);
Load_protection_prepare(6, 0x1302, 0, out, &error);
tick(10, 1, 200); CHECK(!(LOAD_STATUS & 2) && ump_dac_ramp > ramp);
Load_protection_prepare(6, 0x1302, 2, out, &error);
load_count_ramp = time_dac / 6; tick(5, 1, 200); CHECK(load_state == LOAD_RELOAD);
/* Calibration must not replace the forced 4 mA output during reload. */
Commands |= 0x200; DAC_cal() = 4095; tick(5, 1, 200);
CHECK(modbus[123] == DAC_min() && modbus[0x1C] == 40);
/* A calibration code preceding START must be replaced by 4 mA first. */
setup(5); Commands = 0x200; DAC_cal() = 4095;
tick(10, 1, 200); CHECK(modbus[123] == 4095 && !load_zero_sent);
Commands = 0x280; tick(1, 1, 200); CHECK(!test_starts);
tick(4, 1, 200); CHECK(!test_starts && modbus[123] == DAC_min());
tick(1, 1, 200); CHECK(test_starts == 1);
/* Timeout while STOP is held cannot initiate the pending retry. */
setup(5); Commands = 0x80; tick(10, 1, 0);
load_count_ramp = time_dac / 6; tick(5, 1, 0);
CHECK(load_state == LOAD_RELOAD);
tick(1500, 0, 0); CHECK(test_starts == 1 && test_stop);
tick(1, 1, 0); CHECK(test_starts == 2 && !test_stop);
Load_control_init(); CHECK(!load_simulation);
puts("PASS: production DAC control, timeout, CAN slots, STOP ordering, five attempts, simulation");
return 0;
}
+16
View File
@@ -0,0 +1,16 @@
"""Compile the production DAC.c against GPIO/IRQ mocks; no hardware writes."""
import os
from pathlib import Path
import shutil
import subprocess
import tempfile
root = Path(__file__).resolve().parents[2]
compiler = os.environ.get('CC') or shutil.which('gcc') or 'C:/msys64/mingw64/bin/gcc.exe'
os.environ['PATH'] = str(Path(compiler).resolve().parent) + os.pathsep + os.environ['PATH']
with tempfile.TemporaryDirectory(prefix='load-control-') as tmp:
exe = Path(tmp) / 'test.exe'
subprocess.run([compiler, '-Wall', '-Wextra', '-std=c99',
'-Itests/load_control/stubs', '-ISource/Internal/Include',
'tests/load_control/load_control_test.c', '-o', str(exe)], cwd=root, check=True)
subprocess.run([str(exe)], check=True, timeout=10)

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