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:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -7,3 +7,7 @@
|
||||
/Bin/Rs232NoFifoTest/
|
||||
/Bin/UartFifoTest/
|
||||
/Bin/*.pending
|
||||
/UKSSTMS320F28335/Generated/
|
||||
/Bin/FirmwareIdentity/
|
||||
/Bin/*.bin.manifest.json
|
||||
/Bin/*.bin.sha256
|
||||
|
||||
BIN
Bin/GAS_M95M02/UKSSTMS320F28335_GAS_M95M02.bin
Normal file
BIN
Bin/GAS_M95M02/UKSSTMS320F28335_GAS_M95M02.bin
Normal file
Binary file not shown.
41
Bin/GAS_M95M02/UKSSTMS320F28335_GAS_M95M02.bin.manifest.json
Normal file
41
Bin/GAS_M95M02/UKSSTMS320F28335_GAS_M95M02.bin.manifest.json
Normal file
@@ -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"
|
||||
}
|
||||
1
Bin/GAS_M95M02/UKSSTMS320F28335_GAS_M95M02.bin.sha256
Normal file
1
Bin/GAS_M95M02/UKSSTMS320F28335_GAS_M95M02.bin.sha256
Normal file
@@ -0,0 +1 @@
|
||||
a1c186e5f114ea2f77ac5f18a60424c79f53b1e3a49e0343ac9bd96979f0347e UKSSTMS320F28335_GAS_M95M02.bin
|
||||
16
Bin/GAS_M95M02/profile-comparison.json
Normal file
16
Bin/GAS_M95M02/profile-comparison.json
Normal 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
Doc/EEPROM_LAYOUT.md
Normal file
136
Doc/EEPROM_LAYOUT.md
Normal 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
Doc/FIRMWARE_IDENTITY.md
Normal file
167
Doc/FIRMWARE_IDENTITY.md
Normal 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 прочитайте версию снова.
|
||||
97
Doc/GAS_167_M95M02.md
Normal file
97
Doc/GAS_167_M95M02.md
Normal file
File diff suppressed because one or more lines are too long
241
Doc/GAS_MODBUS_PROJECT_COMPARISON.md
Normal file
241
Doc/GAS_MODBUS_PROJECT_COMPARISON.md
Normal file
File diff suppressed because one or more lines are too long
115
Doc/UMP_VOLTAGE_PROTECTION.md
Normal file
115
Doc/UMP_VOLTAGE_PROTECTION.md
Normal 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
F28335.cmd
12
F28335.cmd
@@ -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 */
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
/* 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--;
|
||||
|
||||
/* Îòñóòñòâèå êîìàíäû ïóñêà — íà÷àëî íîâîãî ñåàíñà, ñáðàñûâàåò îøèáêó.
|
||||
* 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);
|
||||
|
||||
/* Ïîâòîðíûé 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 (++load_count_dac >= period_dac) {
|
||||
load_count_dac = 0;
|
||||
if (WAKEpowse) {
|
||||
--WAKEpowse;
|
||||
|
||||
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;
|
||||
} }
|
||||
|
||||
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);
|
||||
if (WAKEpowse && !(WAKEpowse % (DAC_FREQ / 2))) {
|
||||
Init_DAC(); load_init_count = 1;
|
||||
}
|
||||
|
||||
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 {
|
||||
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;
|
||||
}
|
||||
else
|
||||
{ count_load = 0;
|
||||
sinus=0;
|
||||
x = (x+1)&0x3FF;
|
||||
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;
|
||||
}
|
||||
|
||||
/* Ñíèìîê ñòàäèè ðàçãîíà äëÿ ñîïîñòàâëåíèÿ êîìàíäû CAN,
|
||||
* îæèäàåìîãî òîêà è êîäà, äåéñòâèòåëüíî îòïðàâëåííîãî ÖÀÏ. */
|
||||
ump_dac_ramp = count_load;
|
||||
} 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(!x || cInitDac)
|
||||
{
|
||||
Init_DAC(); x=1; cInitDac=0;
|
||||
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);
|
||||
}
|
||||
else
|
||||
{
|
||||
kod = (int)sinus;
|
||||
}
|
||||
}
|
||||
/* Äèàãíîñòèêà äîñòóïíà îáû÷íûì 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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
Source/Internal/Include/eeprom_config.h
Normal file
15
Source/Internal/Include/eeprom_config.h
Normal 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
Source/Internal/Include/gas167.h
Normal file
28
Source/Internal/Include/gas167.h
Normal 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
|
||||
@@ -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] // àðêîñòü ñèãíàëüíûõ ëàìïî÷åê
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
Source/Internal/gas167.c
Normal file
63
Source/Internal/gas167.c
Normal 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
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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; // Óïðàâëàþùèé òîê ÑÈÔÓ
|
||||
} }
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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,6 +395,15 @@ void SPISE2P_DRV_tick(SPISE2P_DRV *eeprom)
|
||||
case 15:
|
||||
/* Send Address */
|
||||
|
||||
#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;
|
||||
|
||||
File diff suppressed because one or more lines are too long
1
UKSSTMS320F28335/.gitignore
vendored
1
UKSSTMS320F28335/.gitignore
vendored
@@ -3,3 +3,4 @@
|
||||
/.launches/
|
||||
/targetConfigs/
|
||||
/project.log
|
||||
/Release/
|
||||
|
||||
@@ -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
|
||||
@@ -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
UKSSTMS320F28335/firmware_identity.json
Normal file
4
UKSSTMS320F28335/firmware_identity.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"product": "Balsam-167-PM35",
|
||||
"target": "TMS320F28335"
|
||||
}
|
||||
9
UKSSTMS320F28335/firmware_version.h
Normal file
9
UKSSTMS320F28335/firmware_version.h
Normal 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
|
||||
@@ -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
UKSSTMS320F28335/prepare_identity.py
Normal file
48
UKSSTMS320F28335/prepare_identity.py
Normal 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)))
|
||||
@@ -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%
|
||||
@@ -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"
|
||||
@@ -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
tests/eeprom_profiles/run_test.py
Normal file
21
tests/eeprom_profiles/run_test.py
Normal 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
tests/eeprom_profiles/spi_test.c
Normal file
47
tests/eeprom_profiles/spi_test.c
Normal 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;
|
||||
}
|
||||
22
tests/eeprom_profiles/stubs/DSP2833x_Device.h
Normal file
22
tests/eeprom_profiles/stubs/DSP2833x_Device.h
Normal file
@@ -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
tests/firmware_identity/check_build.py
Normal file
42
tests/firmware_identity/check_build.py
Normal 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
tests/firmware_identity/port_test.c
Normal file
33
tests/firmware_identity/port_test.c
Normal 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
tests/gas167/check_image.py
Normal file
48
tests/gas167/check_image.py
Normal 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
tests/gas167/gas167_test.c
Normal file
196
tests/gas167/gas167_test.c
Normal 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
tests/gas167/run_test.py
Normal file
51
tests/gas167/run_test.py
Normal 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
tests/load_control/load_control_test.c
Normal file
103
tests/load_control/load_control_test.c
Normal 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
tests/load_control/run_test.py
Normal file
16
tests/load_control/run_test.py
Normal 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)
|
||||
9
tests/load_control/stubs/DSP2833x_Device.h
Normal file
9
tests/load_control/stubs/DSP2833x_Device.h
Normal file
@@ -0,0 +1,9 @@
|
||||
#ifndef TEST_DEVICE_H
|
||||
#define TEST_DEVICE_H
|
||||
struct bits { unsigned int GPIO59, GPIO60, GPIO63; };
|
||||
struct reg { struct bits bit; };
|
||||
static struct { struct reg GPBSET, GPBCLEAR; } GpioDataRegs;
|
||||
static unsigned int __disable_interrupts(void) { return 0; }
|
||||
static void __restore_interrupts(unsigned int saved) { (void)saved; }
|
||||
static void DSP28x_usDelay(unsigned long n) { (void)n; }
|
||||
#endif
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user