feat: add M95M02-only GAS and EEPROM profiles

Expose the existing registers through Modbus and service CAN using one GAS provider only in M95M02 builds.
Include the EEPROM layout/driver, LOAD protection and calibration, firmware identity and shared CCS build workflow.
Add transport/profile and SCI8 validation tests, documentation and the checked M95M02 firmware image.
This commit is contained in:
2026-10-09 16:17:37 +03:00
parent c6c9128238
commit 5d9818291b
63 changed files with 2307 additions and 365 deletions

4
.gitignore vendored
View File

@@ -7,3 +7,7 @@
/Bin/Rs232NoFifoTest/
/Bin/UartFifoTest/
/Bin/*.pending
/UKSSTMS320F28335/Generated/
/Bin/FirmwareIdentity/
/Bin/*.bin.manifest.json
/Bin/*.bin.sha256

Binary file not shown.

View 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"
}

View File

@@ -0,0 +1 @@
a1c186e5f114ea2f77ac5f18a60424c79f53b1e3a49e0343ac9bd96979f0347e UKSSTMS320F28335_GAS_M95M02.bin

View File

@@ -0,0 +1,16 @@
[
{
"after_bytes": 32472,
"profile": 0,
"gas_added_bytes": 0,
"binary_identical": true,
"before_bytes": 32472
},
{
"after_bytes": 33174,
"profile": 1,
"gas_added_bytes": 410,
"binary_identical": false,
"before_bytes": 32764
}
]

136
Doc/EEPROM_LAYOUT.md Normal file
View File

@@ -0,0 +1,136 @@
# EEPROM 167: выбор AT25256B / M95M02
Маркировка платы `ATMLH516 5EBL` определяет AT25256B: **256 Кбит**, то есть
**32 768 байт**. Расшифровка и характеристики приведены в
[даташите Microchip](https://ww1.microchip.com/downloads/aemDocuments/documents/MPD/ProductDocuments/DataSheets/AT25128B-AT25256B-128-256-Kbit-SPI-Serial-EEPROM-Industrial-Grade-Data-Sheet-DS20006193.pdf).
Константы общей разметки находятся в
[templates/include/balsam167_eeprom_layout.h](../../newProject/SETGUI/third_party/templates/c/set-protocol/include/balsam167_eeprom_layout.h).
`spise2p.h` использует их для физического размера, количества слов и длины
страницы. `message.c` — для адреса параметров. Размер массива параметров
проверяется относительно `ANSWER_LEN` на этапе компиляции.
`F28335.cmd` включает тот же заголовок и экспортирует символы `_EEPROM_*` в
`.map`. Путь к templates/c/set-protocol добавлен в search path линкера CLI и CCS.
EEPROM не стала секцией CPU MEMORY: SPI-образ загружается в RAM загрузчиком.
Расположение `.text`, `.cinit`, `.econst`, `.switch` не менялось.
| Область | Начало, байт | Конец, исключая | Размер |
|---|---:|---:|---:|
| Прошивка SCI8 | `0x0000` | `0x7EFC` | 32 508 |
| Параметры | `0x7EFC` | `0x7FFE` | 258 |
| Неиспользуемое слово | `0x7FFE` | `0x8000` | 2 |
`check_firmware.ps1` получает размер и резерв из общего заголовка и сверяет
резерв с `ANSWER_LEN`. При нестандартном checkout передайте `-TemplatesRoot`;
`build_logger.ps1` делает это автоматически. Исторические параметры не перенесены.
Чтение с платы, единицы адресов, порядок байтов и сохранение дампа описаны в
[руководстве SETGUI](../../newProject/SETGUI/docs/BALSAM167_EEPROM.md).
Для старой прошивки reader использует CMD_PEEK (2 байта за запрос).
Версия 0.1.1 также поддерживает блочное чтение FC03, регистры 0x8000..0xBFFF,
до 120 слов (240 байт) за запрос. Полный дамп требует 137 запросов.
Границы и формат заданы в templates, GUI использует тонкий Python-порт.
Блочное чтение выполняется с разрешёнными прерываниями для работы SPI Timer2.
Запись через это окно запрещена; параметры и их расположение не изменяются.
Размер каждого нового BIN проверяется при сборке; прежний вариант с одним
PEEK занимал 32 286 байт. Точный размер новой сборки указан в её manifest.
Для реального увеличения памяти потребовались бы другая микросхема, проверка
SPI-загрузчика и драйвера, а не только правка линкера.
## Выбор типа памяти (прошивка 0.1.2)
По умолчанию остаётся AT25256B. Автоматического определения ёмкости нет:
контрольные записи для определения типа не выполняются. Выбранный тип должен
совпадать с физической микросхемой до запуска программы и чтения параметров.
### CCS12
Откройте `Source/Internal/Include/eeprom_config.h`:
```c
#define EEPROM_M95M02 0 /* AT25256B, 32 КиБ */
/* Для установленной M95M02 измените значение на 1. */
```
После изменения выполните **Project → Clean**, затем **Rebuild**. Этот файл
читают драйвер, обработчик команд и F28335.cmd; post-build использует тот же
выбор. Не задавайте противоречащий ему отдельный define только компилятору CCS:
тогда компилятор, линкер и проверка BIN могут получить разные настройки.
### Сборка скриптом
```powershell
./build_logger.ps1 -Eeprom AT25256B -OutputDirectory ./Bin/EepromProfiles/AT25256B -ImageName UKSSTMS320F28335_AT25256B
./build_logger.ps1 -Eeprom M95M02 -OutputDirectory ./Bin/EepromProfiles/M95M02 -ImageName UKSSTMS320F28335_M95M02
```
Параметр скрипта передаётся компилятору, линкеру и проверке размера; исходный
config не меняется. Скрипт по умолчанию собирает AT25256B. Рядом с BIN создаются
manifest и SHA-256; поле `eeprom` в manifest различает аппаратные варианты.
UART остаётся 115200; алгоритм UART и FIFO не изменялся.
| Параметр | AT25256B | M95M02-DR |
|---|---:|---:|
| Ёмкость, байт | 32768 | 262144 |
| Адрес SPI | 2 байта | 3 байта |
| Страница записи, байт | 64 | 256 |
| Начало параметров, байт | 0x7EFC | 0x3FEFC |
| Начало параметров, слова | 0x3F7E | 0x1FF7E |
| Конец параметров, исключая | 0x7FFE | 0x3FFFE |
| Максимальная область BIN | 32508 | 261884 |
Последние два байта не используются. У M95M02 старшие адресные биты передаются
отдельным восьмибитным тактом, затем младшие 16 бит. Адреса и длины драйвера
расширены до 32 бит только в сборке M95M02. Запись делится по физическим страницам;
готовность определяется WIP в регистре состояния. На плате с новым чипом ещё
нужна проверка записи/считывания и холодного SPI boot; тесты на ПК не заменяют её.
### SETGUI
На вкладках **EEPROM 167** и **Загрузка / запуск / прошивка** есть синхронный
чекбокс **M95M02 · 256 КиБ**. Снято — AT25256B · 32 КиБ. Он выбирает протокол
и допустимый диапазон ПК, но не перепрограммирует тип EEPROM работающей платы.
Во время чтения/загрузки переключатель блокируется.
Для полного дампа нажмите «Вся EEPROM»: 32768 либо 262144 байта. Длина блока
остаётся 240 байт. M95M02 требует блочного чтения; автоматический переход на
PEEK запрещён. Неподходящая прошивка должна отклонить запрос, а не вернуть
повторяющиеся первые 32 КиБ как полный дамп. Дамп можно передать кнопкой
«Сравнить с файлом…» напрямую в сравнение BIN.
При загрузке BIN проверяется поле `eeprom`, если оно присутствует в manifest.
Старые файлы без этого поля не позволяют проверить тип. Команда «В SEEPROM»
использует отдельный подкод M95M02; старая прошивка его не исполняет. Операция
может завершиться таймаутом при несовпадении типа; это не успешная запись.
GUI ограничивает отдельную загрузку в RAM существующим пределом 199993 байт:
ёмкость EEPROM 256 КиБ сама по себе не увеличивает буфер загрузки или RAM CPU.
### Замена микросхемы и параметры
Сначала сохраните полный дамп старой EEPROM и настройки платы. После установки
M95M02 запустите её сборку через CCS, запишите соответствующий BIN и сохраните
параметры штатной командой Save. Параметры автоматически не мигрируют с 0x7EFC
на 0x3FEFC. Простое копирование старого полного дампа в начало новой памяти
не переносит настройки в их новую область и оставляет старый вариант прошивки.
### Общий протокол templates
Реализация и границы: `balsam167_eeprom_layout.h`, `balsam167_eeprom_block.h`,
`periph28335.c`. Публичные функции `*_profile` получают 0 (AT25256B) или 1
(M95M02); старые функции и структура reader из четырёх uint32_t сохранены.
Python — тонкий ctypes-порт. Профили ПМ67/2812 и STM этим выбором не изменяются.
Для M95M02 используется частное расширение окна FC03: адрес 0x8000…0xFFFF,
quantity = 0x8000 | (bank << 8) | word_count, bank 0…3, word_count 1…120.
Каждый запрос содержит свой банк 64 КиБ; общего переключаемого банкового
регистра нет. Блок не пересекает границу банка. Это не стандартное количество
регистров Modbus — пользоваться следует общим API templates.
Подкод EXTEND записи: 4 для AT25256B, 0x44 для M95M02. Адрес назначения в словах,
размер в байтах. Обе стороны проверяют границу области прошивки.
Проверки: C-тест полного дампа 256 КиБ с нечётного адреса, все переходы банков,
CRC и выход за границы; Qt-тесты выбора и запрета fallback; host-тест реального
SPI-автомата `python tests/eeprom_profiles/run_test.py`; сборка обоих вариантов TI.

167
Doc/FIRMWARE_IDENTITY.md Normal file
View File

@@ -0,0 +1,167 @@
# 167: версия работающей прошивки, SHA-256 и выпуск
## Что подключено
167 использует общий `templates/c/firmware-info`, а не копию Climate.
Работающий ПМ35 отдаёт 12 регистров версии через RS и служебный CAN.
После сборки на компьютере создаются SHA-256 BIN и JSON-паспорт.
Общая подробная инструкция, включая перенос на TMS320F2812 и STM32:
[BUILD_IDENTITY.md](../../newProject/SETGUI/third_party/templates/c/firmware-info/BUILD_IDENTITY.md).
| Файл проекта | Роль |
|---|---|
| `UKSSTMS320F28335/firmware_version.h` | Редактируемый номер выпуска: FIRMWARE_VERSION_MAJOR/MINOR/PATCH |
| `UKSSTMS320F28335/firmware_identity.json` | Product и target; версия берётся из C-заголовка |
| `Source/Internal/shared_ump.c` | Подключение общей таблицы версии и привязка к UMP ПМ35 |
| `UKSSTMS320F28335/prepare_identity.py` | Генерация идентичности из C-заголовка перед CCS Debug/Release |
| `build_logger.ps1` | Полный CLI build: prepare, compile, link, SCI8, size check, seal |
| `build_bin.bat` | Debug post-build: проверка SCI8 и паспорт конечного BIN |
| `tests/firmware_identity/port_test.c` | Host-проверка привязки версии к реальному порту 167 |
Номер `0.1.0` — начальная версия разработки этого механизма; он не выдаётся за
историческую версию предыдущих BIN. Назначайте release-номер вручную перед выпуском.
## Сборка из PowerShell
Из корня 167:
```powershell
./build_logger.ps1 -OutputDirectory "$PWD/Bin/FirmwareIdentity" `
-ImageName UKSSTMS320F28335
```
По умолчанию применяется `-O2 --opt_for_speed=0`. Результаты:
```text
Bin/FirmwareIdentity/UKSSTMS320F28335.bin
Bin/FirmwareIdentity/UKSSTMS320F28335.bin.manifest.json
Bin/FirmwareIdentity/UKSSTMS320F28335.bin.sha256
Bin/FirmwareIdentity/generated/firmware_identity.h
Bin/FirmwareIdentity/generated/firmware_identity.json
```
Проверка файла перед передачей:
```powershell
python ../newProject/SETGUI/third_party/templates/c/firmware-info/tools/identity.py verify `
--image Bin/FirmwareIdentity/UKSSTMS320F28335.bin `
--manifest Bin/FirmwareIdentity/UKSSTMS320F28335.bin.manifest.json
```
CLI принимает `-TemplatesRoot`, если общий checkout расположен иначе. Все
исходники общего механизма собираются непосредственно из этого checkout.
## Сборка CCS
В `.cproject` добавлены pre-build и include paths `${BuildDirectory}/generated` и
`templates/c/firmware-info/include`. После изменения настроек закройте/откройте
проект в IDE, затем выполните Clean/Rebuild.
Pre-build требует Python 3.10+ и Git в PATH процесса CCS. `Generated` принадлежит
этому проекту: не запускайте Debug/Release одновременно. Для параллельных
сборок используйте CLI с разными output-каталогами.
Штатный Debug post-build создаёт в `Bin`:
```text
UKSSTMS320F28335_<commit>.bin
UKSSTMS320F28335_<commit>.bin.manifest.json
UKSSTMS320F28335_<commit>.bin.sha256
```
Имя BIN исторически содержит HEAD без dirty-суффикса; достоверные признаки dirty
находятся внутри ответа версии и паспорта. Не определяйте чистоту только по имени.
Release-конфигурация получает embedded identity через pre-build, но её старый
проект не имел автоматического BIN post-build. Для готового проверенного BIN с
паспортом используйте описанный CLI либо настройте release-конверсию по общей
инструкции; нельзя запускать Debug `build_bin.bat` на Release и ожидать другой `.out`.
Если CCS пишет `Error ... (ignored)`, сборка не считается успешной: убедитесь,
что последняя проверка и `seal` завершились без ошибок. Старый BIN с таким же
именем может оставаться на диске после неудачной сборки.
## Как прочитать версию
По прямому RS232/RS485: Modbus **03**, адрес начала **4608 (`0x1200`)**, **12 слов**.
Это zero-based адрес в запросе, а не обозначение `4xxxx` в интерфейсе терминала.
Адрес ведомого остаётся текущим `CNTRL_ADDR`. Возвращается стандартный RTU-ответ
с 24 байтами данных и CRC; дополнительного legacy padding в этом окне нет.
| Адрес | Содержимое |
|---|---|
| `0x1200` | Версия формата = 1 |
| `0x1201..0x1203` | Major, minor, patch |
| `0x1204` | Год UTC |
| `0x1205` | Месяц:день, старший:младший октет |
| `0x1206` | Час:минута UTC |
| `0x1207` | Секунда |
| `0x1208..0x120B` | 8 ASCII-символов Build ID, по два на слово |
По служебному CAN ПМ35: запрос UMP функции 03, `0x1200`, 12 слов; ответ состоит
из шести фрагментов. Используется выбранный Mode и существующий token запроса.
CAN ПМ67 не переназначается. Запись функции 06 в окно версии запрещена.
Старый образ может вернуть исключение или неизвестный контракт. Это не следует
заменять «ожидаемой» версией выбранного BIN. После прошивки переподключитесь или
явно обновите чтение: ответ должен поступить с устройства, а не из кэша GUI.
## Что сравнивать после заливки
Сравните SemVer, все поля UTC и Build ID с паспортом именно загруженного BIN.
`+` означает локальные изменения приложения **или templates**; полный Git SHA
и состояние обоих деревьев записаны в JSON. Проверка SHA на ПК подтверждает
целостность файла, но не заменяет проверку программатором записанной памяти.
В контракте нет аппаратного product ID и хеша всей памяти. Подключённый профиль
должен оставаться ПМ35/167, а файл — `product=Balsam-167-PM35`,
`target=TMS320F28335`. Не переносите файл на ПМ67 только по похожему имени.
## Влияние на память и параметры
С проверенными настройками текущий образ занимает **32 286 байт**, предел —
**32 508**, запас **222 байта**. Механизм добавил 116 байт относительно сборки
без него. Это результат конкретной сборки, не обещание для следующих изменений.
Таблица версии — часть `.econst` прошивки. Её не сохраняют в область параметров;
старые настройки не могут подменить версию после обновления. Размер буфера лога
и размещение EEPROM-параметров не менялись. Дата не разбирается на MCU, SHA-256
не вычисляется на MCU. Проверка размера `check_firmware.ps1` остаётся обязательной.
## Проверки и ограничения
Проверка соответствия паспорта **реально встроенной** таблице в собранном SCI8:
```powershell
python tests/firmware_identity/check_build.py Bin/FirmwareIdentity/UKSSTMS320F28335.bin
```
Этот независимый тест также сверяет SHA и размер, проходит boot-блоки и ищет
точное 12-словное представление версии. Он обнаружит старый объект версии или
паспорт другой сборки. Это тест для 167, не транспортный код для GUI.
Host-тест реального порта (GCC должен быть в PATH):
```powershell
$templates = '../newProject/SETGUI/third_party/templates'
gcc -std=c99 -Wall -Wextra -Werror `
"-I$templates/c/firmware-info/include" -IBin/FirmwareIdentity/generated `
"-I$templates/c/set-protocol" "-I$templates/c/set-protocol/ports/tms320f28335" `
tests/firmware_identity/port_test.c Source/Internal/shared_ump.c `
-o Bin/FirmwareIdentity/port_test.exe
./Bin/FirmwareIdentity/port_test.exe
python tests/ump_logger/test_firmware_image.py
python tests/ump_logger/run_test.py
python tests/ump_logger/run_init_test.py
```
Проверены полный TI build 167, формат SCI8, host-тесты протокола и регистратора,
общие тесты identity/SHA, компиляция общего C-кода TI C28x без FPU и ARM Cortex-M4.
Это не аппаратная проверка TMS2812/STM32 и не полноценная сборка другого проекта.
Физическое чтение нового окна версии и проверку после загрузки нужно выполнить
на стенде. В SETGUI на вкладке «Загрузка / запуск / прошивка» доступны
«Прочитать версию с платы» и «Проверить файл»: показаны SemVer, дата UTC,
Build ID и сравнение с выбранным BIN. Для проверки файла держите рядом его
`.bin.manifest.json`; GUI проверяет product/target, размер и SHA-256 и повторяет
проверку перед передачей. BIN без паспорта отмечается как непроверенный старый
образ; неверный паспорт блокирует загрузку. После RESET прочитайте версию снова.

97
Doc/GAS_167_M95M02.md Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,115 @@
# Контроль роста ЗПТ при пуске УМП
УМП с адресом 5 (`Mode=5`) контролирует ЗПТ1 — CAN-индекс 305
(регистр ПМ67 306). УМП 6 контролирует ЗПТ2 — CAN-индекс 8
(регистр ПМ67 9). Масштаб исходного слова: 1 единица = 1 В.
## Условия и последовательность
Начальный безопасный код задания соответствует 4 мА по `DAC_min()`.
Рост напряжения считается достаточным при ЗПТ **не меньше 30 В**.
Проверяется расчётное задание ЦАП, а не измеренный выходной ток.
Ручная калибровка не подменяет задание при активной команде пуска
или удерживаемом STOP.
Если очередной шаг рампы требует **строго больше 6 мА**, а свежее
CAN-напряжение меньше 30 В, шаг не передаётся в ЦАП. Вместо него:
1. Снимается START, включается STOP, снимается разрешение RES_OUT_2.
2. Положение рампы обнуляется, передаётся код 4 мА без подмены калибровкой.
3. STOP остаётся включённым **1000 мс от момента его установки**.
4. Когда код 4 мА отправлен, прошла секунда и есть свежее ЗПТ,
снимается STOP, сразу выставляется START, затем разрешение.
5. Новая рампа начинается с нулевого положения с исходными уставками
`DAC_go`/`DAC_stop` (штатные значения могут начинать рост с 6 мА).
Первый START входит в лимит: **всего 5 попыток**, не 5 дополнительных.
После пятой неудачной попытки удерживаются STOP и 4 мА, разрешение снято,
шестой START не выдаётся. Ошибка снимается командой `cUMPstart=0`;
затем можно подать новый пуск. Команда RESET имеет приоритет перед START.
Отпускание RESET с оставшимся START=1 само по себе не перезапускает УМП.
Выходами и рампой управляет один обработчик Timer1 (1 мс). Основной цикл
больше не формирует независимые фронты START/STOP. Расчёт/отправка ЦАП
остались с периодом 5 мс. Импульс START длится до 1000 мс, но защита
или RESET прекращают его раньше.
## Отсутствие CAN
Таймаут по умолчанию **600 мс**, диапазон настройки **1…60000 мс**.
Свежесть подтверждается только получением нужного ЗПТ в полном
технологическом CAN-кадре с соответствующим битом маски. Другое
напряжение, сервисные запросы, команды и произвольные CAN-пакеты
таймаут не сбрасывают. До первого значения напряжение считается неизвестным.
При таймауте работающая рампа удерживает последнее положение и отправленный
код. Попытка не считается неудачной, STOP по одному таймауту не включается.
После свежего ЗПТ рост продолжается; если оно меньше 30 В при задании
больше 6 мА, выполняется защитный STOP. Если обмен исчез во время
сброса, STOP держится и после секунды — до свежего напряжения.
Первый пуск также ждёт свежего значения.
Настройка хранится в технологическом регистре `0x52` платы нагрузки.
Значение 0 в старом EEPROM заменяется на 600 мс при запуске.
Для сохранения изменения используйте штатную команду сохранения параметров.
## Диагностика и эмуляция в GUI
Во вкладке **Логгер УМП** добавлены:
- **Защита ЗПТ: прочитать** — состояние защиты, число попыток, напряжение.
- **Эмуляция: нет CAN** — получение своего ЗПТ игнорируется платой;
после заданного таймаута рампа удерживается.
- **Эмуляция: ЗПТ не растёт** — для проверки защиты используется 0 В,
хотя настоящие кадры продолжают поступать. Реальные напряжения в
журнале не подменяются. Этот режим действительно вызывает STOP/пуски.
- **Задать таймаут** — запись значения в плату с обязательным чтением обратно.
Кнопки эмуляции включаются после проверки сигнатуры прошивки. Их положение
меняется только после подтверждённого чтения обратно. Два режима можно
включать независимо; при отсутствии свежести проверка роста приостановлена.
Эмуляция живёт только в RAM, не сохраняется в EEPROM и выключена после
перезагрузки. GUI показывает активную эмуляцию отдельно от реального ЗПТ.
При онлайн-чтении GUI опрашивает защиту каждые 500 мс и показывает
**«НЕИСПРАВНОСТЬ: ЗПТ не достигло 30 В за 5 попыток»**.
Старые прошивки без блока защиты продолжают обычное онлайн-чтение;
эмуляция для них недоступна.
Технологические регистры только для чтения: `0x7C` — статус,
`0x7D` — число попыток. Оба включены в быстрый CAN-цикл.
Статус: бит 0 — ошибка пяти попыток, бит 1 — нет свежего ЗПТ,
бит 2 — сброс перед повтором, бит 3 — работа рампы,
биты 4/5 — активная эмуляция отсутствия CAN/роста ЗПТ.
Локальный блок RTU и служебного CAN: FC03 `0x1300`, ровно 8 слов:
| Смещение | Значение |
|---|---|
| 0 | Сигнатура `0x5550` |
| 1 | Версия 1 |
| 2 | Эмуляция: бит 0 — нет CAN, бит 1 — нет роста |
| 3 | Таймаут, мс |
| 4 | Статус защиты |
| 5 | Число попыток |
| 6 | Последнее реальное CAN-напряжение, В |
| 7 | Возраст значения, мс; 65535 до первого значения |
FC06 допускает запись только `0x1302` (0…3) и `0x1303` (1…60000).
Диагностический обмен не обновляет возраст технологического напряжения.
## Проверки
`python tests/load_control/run_test.py` компилирует непосредственно
`DAC.c` с подменой GPIO/IRQ. Проверяются выбор ЗПТ, маски CAN, таймаут,
удержание кода, граница 30 В, порядок STOP/START, точная пауза 1000 мс,
лимит пяти пусков, RESET, эмуляция и невозможность подмены 4 мА калибровкой.
GUI: `tests/test_ump_voltage_protection.py` в SETGUI плюс регрессии логгера.
Сборка: `build_logger.ps1 -OutputDirectory Bin/VoltageProtection
-ImageName UKSSTMS320F28335_voltage_protection`. Размер проверяется до
публикации BIN; для AT25256B предел — 32508 байт. В режиме оптимизации
функции разнесены по секциям, чтобы линкер удалял неиспользуемые функции.
Это проверки на компьютере. Физический ток 4 мА, уровни разрешения,
STOP и интервал 1 с нужно проверить на стенде с установленной прошивкой.

View File

@@ -69,6 +69,18 @@
*/
/* Serial EEPROM is NOT CPU-addressable RAM/Flash. Do not add a fake MEMORY
* region at address zero: SPI boot copies its table into the RAM areas below.
* Export its physical layout as absolute byte/word symbols for .map/debugging.
* The final SCI8 image size (including boot headers) is checked after hex2000. */
#include "Source/Internal/Include/eeprom_config.h"
#include "include/balsam167_eeprom_layout.h"
_EEPROM_TOTAL_BYTES = BALSAM167_EEPROM_BYTES;
_EEPROM_FIRMWARE_LIMIT_BYTES = BALSAM167_EEPROM_FIRMWARE_BYTES;
_EEPROM_PARAMS_START_WORD = BALSAM167_EEPROM_PARAMS_START_WORD;
_EEPROM_PARAMS_START_BYTE = BALSAM167_EEPROM_FIRMWARE_BYTES;
_EEPROM_PARAMS_END_BYTE = BALSAM167_EEPROM_PARAMS_END_WORD * 2;
MEMORY
{
PAGE 0: /* Program Memory */

View File

@@ -21,12 +21,110 @@ unsigned long WAKEpowse;
unsigned long IMPowse;
unsigned int period_dac, time_dac;
/* Âñå âûõîäû ÓÌÏ è ðàìïà ïðèíàäëåæàò îäíîìó îáðàáîò÷èêó Timer1.
* Îñíîâíîé öèêë áîëüøå íå âûäà¸ò START: èíà÷å îí ìîæåò óâèäåòü êîðîòêèé
* ñïàä êîìàíäû, êîòîðûé ïðîïóñòèò ðàñ÷¸ò ÖÀÏ ñ ïåðèîäîì 5 ìñ. */
#define LOAD_IDLE 0U
#define LOAD_RUN 1U
#define LOAD_RELOAD 2U
#define LOAD_FAULT 3U
#define LOAD_MAX_ATTEMPTS 5U
#define LOAD_VOLTAGE_MIN 30U
#define LOAD_PULSE_MS 1000U
static unsigned int load_state, load_attempts, load_inhibit;
static unsigned int load_count_dac, load_count_ramp, load_init_count;
static unsigned int load_zero_sent, load_stop_asserted, load_initialized;
static float load_go, load_end;
/* 16-áèòíûå ïîëÿ îáùèå ñ CAN ISR. Ñíèìîê è óâåëè÷åíèå âîçðàñòà âûïîëíÿþòñÿ
* ïîä êîðîòêîé áëîêèðîâêîé IRQ; îòïðàâêà SPI ïîä áëîêèðîâêîé íå âûïîëíÿåòñÿ. */
static volatile unsigned int load_voltage, load_voltage_age, load_voltage_seen;
static volatile unsigned int load_simulation;
void Load_control_init(void)
{
load_state = LOAD_IDLE; load_attempts = 0; load_inhibit = 0;
load_count_dac = 0; load_count_ramp = 0; load_init_count = 1;
load_zero_sent = 0; load_stop_asserted = 0; load_initialized = 0;
load_go = 0; load_end = 0;
load_voltage = 0; load_voltage_age = 0; load_voltage_seen = 0;
load_simulation = 0;
IMPowse = 0; WAKEpowse = 0; period_dac = 0; time_dac = 0;
}
/* Âîçðàñò ñáðàñûâàåò òîëüêî íóæíîå íàïðÿæåíèå, à íå ëþáîé CAN-ïàêåò.
* ÓÌÏ 5: ÇÏÒ1, èíäåêñ 305; ÓÌÏ 6: ÇÏÒ2, èíäåêñ 8. Ìàñøòàá 1 Â/åä.
* Ìàñêè ñëîòîâ ñîõðàíÿþò àäðåñà äàæå äëÿ ïðîïóùåííûõ ñëîâ êàäðà. */
void Load_voltage_can(unsigned int header, unsigned int a,
unsigned int b, unsigned int c)
{
unsigned int address = header & 0x1FFFU;
unsigned int selected, i, mask = 0x8000U, data[3];
if (Desk != dsk_LOAD || (load_simulation & 1U)) return;
if (Mode == adr_LOA1) selected = 305U;
else if (Mode == adr_LOA2) selected = 8U;
else return;
data[0] = a; data[1] = b; data[2] = c;
for (i = 0; i < 3; i++, address++, mask >>= 1) {
if ((header & mask) && address == selected) {
load_voltage = data[i];
load_voltage_age = 0;
load_voltage_seen = 1;
}
}
}
/* STOP óäåðæèâàåòñÿ âåñü ñáðîñ, âêëþ÷àÿ îòïðàâêó êîäà 4 ìÀ â ÖÀÏ.
* Ïîâòîðíûé ïóñê ðàçðåø¸í òîëüêî ïîñëå ýòîé îòïðàâêè è 1000 ìñ STOP.
* Ýòî ïîäòâåðæäåíèå ïðîãðàììíîé îòïðàâêè, íå èçìåðåíèå ôèçè÷åñêîãî òîêà. */
static void load_stop(unsigned int state)
{
clear_START_DPTB();
set_STOP_DPTB();
clear_RES_OUT_2();
load_stop_asserted = 1;
load_state = state;
load_count_ramp = 0;
load_zero_sent = 0;
IMPowse = LOAD_PULSE_MS;
}
static void load_start(void)
{
/* Óñòàâêè ôèêñèðóþòñÿ äëÿ êàæäîé ïîïûòêè. Âíóòðåííèé íîëü = 4 ìÀ.
* Èñïðàâëåíà îïå÷àòêà ñòàðîãî îãðàíè÷åíèÿ: îãðàíè÷èâàåì êîíåö ðàìïû,
* à íå çàìåíÿåì íà÷àëüíóþ óñòàâêó íà 16 (÷òî äàâàëî ñêà÷îê äî 20 ìÀ). */
load_go = (float)DAC_go - 4.0;
load_end = (float)DAC_stop - 4.0;
if (load_go < 0) load_go = 0;
if (load_go > 5) load_go = 5;
if (load_end > 16) load_end = 16;
if (load_end <= load_go) load_end = load_go + 1;
ump_dac_go10 = (unsigned int)((load_go + 4.0) * 10.0);
ump_dac_stop10 = (unsigned int)((load_end + 4.0) * 10.0);
load_count_ramp = 0;
load_attempts++;
/* STOP îòïóñêàåì íåïîñðåäñòâåííî ïåðåä START, ðàçðåøåíèå — ïîñëå íåãî.
* Çàäàíèå îñòà¸òñÿ 4 ìÀ äî ïåðâîãî øàãà íîâîé ðàìïû. */
clear_STOP_DPTB();
load_stop_asserted = 0;
set_START_DPTB();
set_RES_OUT_2();
IMPowse = LOAD_PULSE_MS;
load_state = LOAD_RUN;
}
void Setup_DAC_time(void)
{
WAKEpowse = 3L * DAC_FREQ +1;
period_dac = READY_FREQ / DAC_FREQ;
time_dac = LOAD_TIME * DAC_FREQ;
/* Ñòàðûå EEPROM èìåþò íîëü â íîâîì ïàðàìåòðå: èñïîëüçóåì 600 ìñ.
* 1..60000 ìñ ìîæíî ìåíÿòü ÷åðåç ðåãèñòð 0x52 è ñîõðàíÿòü øòàòíî. */
if ((unsigned int)LOAD_CAN_TIMEOUT == 0U ||
(unsigned int)LOAD_CAN_TIMEOUT > 60000U) LOAD_CAN_TIMEOUT = 600;
LOAD_STATUS = 0; LOAD_ATTEMPTS = 0;
load_initialized = 1;
}
void dOUT(int x)
@@ -63,7 +161,7 @@ void Init_DAC(void)
toggle_RES_OUT_1();
}
void Anal_output(long vrot, long maxx)
static void anal_output(long vrot, long maxx, int calibration)
{
long out;
static int x;
@@ -72,7 +170,7 @@ void Anal_output(long vrot, long maxx)
x=0;// !x; òùåòíî áûòèå
if(cCalibrDac) out=DAC_cal();
if(calibration && cCalibrDac) out=DAC_cal();
out = out & 0xFFF;
@@ -99,79 +197,145 @@ Log_to_mem(modbus[0x1D]);
}
void Load_runner()
/* Ñîõðàí¸í ïóáëè÷íûé èíòåðôåéñ; çàùèòíûé ñáðîñ âûçûâàåò âíóòðåííþþ
* ôóíêöèþ ñ calibration=0, ÷òîáû ðó÷íàÿ êàëèáðîâêà íå ïîäìåíèëà 4 ìÀ. */
void Anal_output(long vrot, long maxx)
{
static unsigned int count_dac=0, count_load=0, x=1;
static unsigned int preStart=0;
static float dac_go,dac_stop;
float sinus=0;
int kod;
if (maxx > 0) anal_output(vrot, maxx, 1);
}
if(IMPowse) IMPowse--;
if(++count_dac >= period_dac)
{
count_dac=0;
void Load_runner(void)
{
unsigned int saved, commands, voltage, fresh, timeout;
unsigned int start, reset, active = 0;
float sinus = 0;
if(WAKEpowse)
{
/* 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;
}

View File

@@ -6,3 +6,13 @@ void Setup_DAC_time(void);
/* Ñ÷¸ò÷èê ïóñêîâîãî èìïóëüñà è âûäåðæêè èíèöèàëèçàöèè äîñòóïíû ëîããåðó. */
extern unsigned long IMPowse, WAKEpowse;
extern unsigned int period_dac, time_dac;
/* Call once before IRQ; complete technological CAN packets update voltage. */
void Load_control_init(void);
void Load_voltage_can(unsigned int header, unsigned int a,
unsigned int b, unsigned int c);
/* 0x1300/8 words: signature, version, simulation, timeout, status, attempts,
* voltage, age. Only 0x1302 (simulation) and 0x1303 (timeout) are writable. */
unsigned int Load_protection_prepare(unsigned int function, unsigned int address,
unsigned int value, unsigned int *out, unsigned int *error);

View File

@@ -10,8 +10,8 @@
4876, 5005, 4876, 4847, 1898, 1873, 2535, 2502, // Áîðò 1
// ÓÌÏ UA UC IA IC 20mA O 4mA O 20mA I 4mA I
3820, 3790, 3505, 3380, 1736, 683, 854, 14, // Αξπς 0 1062 1980
3835, 3785, 3522, 3332, 1745, 689, 860, 17, // Αξπς 1 1720 2405
7580, 7540, 2310, 2420, 1736, 683, 854, 14, // Αξπς 0 1062 1980
7580, 7540, 2310, 2420, 1745, 689, 860, 17, // Αξπς 1 1720 2405
// ÁÊÑÑÄ 100 Ohm 150 Ohm
1281, 1272, 0, 0, 622, 2263, 0, 0,

View File

@@ -0,0 +1,15 @@
#ifndef BALSAM167_EEPROM_CONFIG_H
#define BALSAM167_EEPROM_CONFIG_H
/* Build-time checkbox: 0 = AT25256B (32 KiB), 1 = M95M02-DR (256 KiB).
* This must match the soldered part BEFORE startup loads saved parameters.
* CCS: change the value below, then Project > Clean and Rebuild.
* Command-line builds may override EEPROM_M95M02 for that build only.
* A GUI checkbox cannot change SPI ROM boot or startup initialization.
*/
#ifndef EEPROM_M95M02
#define EEPROM_M95M02 0
#endif
#if EEPROM_M95M02 != 0 && EEPROM_M95M02 != 1
#error EEPROM_M95M02_must_be_0_or_1
#endif
#endif

View File

@@ -0,0 +1,28 @@
#ifndef BALSAM167_GAS_H
#define BALSAM167_GAS_H
#include "eeprom_config.h"
#include <stdint.h>
/* Board profile 167/v1, word addresses, independent of the wire transport.
* The original register and UMP addresses remain valid in both builds.
* Only the physically fitted M95M02 version exposes the additional regions. */
#if EEPROM_M95M02
#define GAS167_REG_BASE 0x5000U
#define GAS167_REG_COUNT 128U
#define GAS167_INFO_BASE 0x5F00U
#define GAS167_INFO_WORDS 8U
#define GAS167_MAX_WORDS 124U
#define GAS167_PROFILE_VERSION 1U
#define GAS167_CAPABILITIES 7U /* bit 0 RTU, bit 1 service CAN, bit 2 FC06 */
/* Called by the main-loop transports under their snapshot IRQ lock.
* Capacity: at least GAS167_MAX_WORDS uint16_t words.
* RTU reads: 1..124 words; paired service CAN reads: 2..124, even.
* Successful reads return their count. Writes/errors return address/value.
* error uses Modbus codes 0 (success), 1 (function), 2 (address), 3 (value).
* No EEPROM I/O, allocation, or changes to the existing logger. */
int gas167_contains(uint16_t address);
unsigned gas167_prepare(unsigned function, uint16_t address, uint16_t value,
int can, uint16_t *reply, unsigned *error);
#endif
#endif

View File

@@ -95,6 +95,11 @@
#define DAC_go modbus[0x50] // íà÷àëî çàðàäà
#define DAC_stop modbus[0x51] // êîíåö çàðàäà
/* LOAD-only persistent CAN timeout (ms), read-only status and attempts. */
#define LOAD_CAN_TIMEOUT modbus[0x52]
#define LOAD_STATUS modbus[0x7C]
#define LOAD_ATTEMPTS modbus[0x7D]
#define Cancount (modbus+0x60) // ïàóçà ìåæäó I ïîñûëêàìè CAN
#define Bright (modbus+0x62) // àðêîñòü ñèãíàëüíûõ ëàìïî÷åê
#define Brightness modbus[0x62] // àðêîñòü ñèãíàëüíûõ ëàìïî÷åê

View File

@@ -2,8 +2,19 @@
#define __SPISE2P_H__
/* Physical EEPROM layout shared with the linker and SETGUI. */
#include "eeprom_config.h"
#include "include/balsam167_eeprom_layout.h"
// ¨ìêîñòü ïàìàòè â áàéòàõ
#define SEEPROM_LEN 0x10000
/* Preserve compact 16-bit arithmetic for the original 32-KiB build.
* Only the large part needs 32-bit word addresses and transfer lengths. */
#if EEPROM_M95M02
typedef unsigned long SE2P_SIZE;
#else
typedef unsigned int SE2P_SIZE;
#endif
#define SEEPROM_LEN BALSAM167_EEPROM_BYTES
#define NULL 0
@@ -58,8 +69,10 @@ typedef struct {
/* Object declaration */
typedef struct {
SE2P_DATA *msgPtr;
unsigned int csr; /* control/status register */
/* Main loop publishes the descriptor before setting csr; Timer2 consumes it.
* Both accesses are volatile so optimization cannot publish the request first. */
SE2P_DATA * volatile msgPtr;
volatile unsigned int csr; /* control/status register */
void (*init)(void *);
void (*tick)(void *);
void (*csset)(void);
@@ -85,12 +98,12 @@ void spiSe2pWrite(SPISE2P_DRV *se2p, SE2P_DATA *data);
void spiSe2pRead(SPISE2P_DRV *se2p, SE2P_DATA *data);
#if(SPISE2P_DATA_WIDTH==SIXTEEN_BIT)
#define PROM_LEN 0x8000
#define PAGE_LEN 0x20
#define PROM_LEN BALSAM167_EEPROM_WORDS
#define PAGE_LEN (BALSAM167_EEPROM_PAGE_BYTES / 2)
#define WORD_LEN 2
#else
#define PROM_LEN 0x4000
#define PAGE_LEN 0x40
#define PROM_LEN BALSAM167_EEPROM_BYTES
#define PAGE_LEN BALSAM167_EEPROM_PAGE_BYTES
#define WORD_LEN 1
#endif
@@ -105,7 +118,7 @@ void InitSeeprom(void);
** adres = 0..0x4000, åñëè äëèíà ñëîâà 16 áèò **
** buf - óêàçàòåëü íà ïàìàòü, îòêóäà ïèñàòü. **
** size - äëèíà áëîêà â áàéòàõ. Ïî-ëþáîìó â áàéòàõ! */
void Seeprom_write(unsigned int adres, unsigned int buf[], unsigned int size);
void Seeprom_write(SE2P_SIZE adres, unsigned int buf[], SE2P_SIZE size);
/* ×òåíèå áëîêà èç SEEPROM. Ïàðàìåòðû òàêîâû: **
** adres - àäðåñ â åïðîìêå, îòêóäà ÷èòàòü. **
@@ -113,7 +126,7 @@ void Seeprom_write(unsigned int adres, unsigned int buf[], unsigned int size);
** adres = 0..0x4000, åñëè äëèíà ñëîâà 16 áèò **
** buf - óêàçàòåëü íà ïàìàòü, êóäà ÷èòàòü. **
** size - äëèíà áëîêà â áàéòàõ. Ïî-ëþáîìó â áàéòàõ! */
void Seeprom_read(unsigned int adres, unsigned int buf[], unsigned int size);
void Seeprom_read(SE2P_SIZE adres, unsigned int buf[], SE2P_SIZE size);
#endif

View File

@@ -398,7 +398,16 @@ void extendbios(RS_DATA *rs_arr)
switch ( code )
{
// Ïðîøèâàåì EPROM Èç RAM
case 4: Seeprom_write(Address1,(unsigned int*)Address2,Length);
#if EEPROM_M95M02
/* 0x44 is intentionally rejected by old firmware. Conversely this build
* rejects legacy code 4, preventing writes with the wrong GUI profile. */
case 0x44:
#else
case 4:
#endif
if (!Length || Address1 >= BALSAM167_EEPROM_FIRMWARE_BYTES/2UL ||
Length > BALSAM167_EEPROM_FIRMWARE_BYTES-Address1*2UL) return;
Seeprom_write(Address1,(unsigned int*)Address2,Length);
break;
// ×èòàåì èç EPROM â RAM
case 5: Seeprom_read(Address1,(unsigned int*)Address2,Length);

View File

@@ -12,7 +12,9 @@
#include "message.h"
#include "peripher.h"
#include "log_to_mem.h"
#include "DAC.h"
#include "ump_can_protocol.h"
#include "gas167.h"
// Prototype statements for functions found within this file.
@@ -234,11 +236,18 @@ void Handlai(volatile struct ECAN_MBOXES * ECanMboxes)
Data[1] = (loword>>16) & 0xffff;
Data[2] = (loword ) & 0xffff;
if(bit[0]) if(adr < ANSWER_LEN) modbus[adr] = Data[0]; adr++;
if(bit[1]) if(adr < ANSWER_LEN) modbus[adr] = Data[1]; adr++;
if(bit[2]) if(adr < ANSWER_LEN) modbus[adr] = Data[2];
if(bit[0]) if(adr < ANSWER_LEN &&
!(Desk == dsk_LOAD && (adr == 0x7CU || adr == 0x7DU))) modbus[adr] = Data[0]; adr++;
if(bit[1]) if(adr < ANSWER_LEN &&
!(Desk == dsk_LOAD && (adr == 0x7CU || adr == 0x7DU))) modbus[adr] = Data[1]; adr++;
if(bit[2]) if(adr < ANSWER_LEN &&
!(Desk == dsk_LOAD && (adr == 0x7CU || adr == 0x7DU))) modbus[adr] = Data[2];
/* Ôèêñèðóåì ïðèìåí¸ííóþ êîìàíäó äî âûõîäà èç îáðàáîò÷èêà CAN. */
/* A short frame cannot confirm voltage freshness. Service frames
* UMP_MARKER return above and do not refresh this timeout. */
if (ECanMboxes->MBOX1.MSGCTRL.bit.DLC == 8)
Load_voltage_can((unsigned int)(hiword >> 16), Data[0], Data[1], Data[2]);
ump_log_can((unsigned int)(hiword >> 16), Data[0], Data[1], Data[2]);
led2_toggle();
}
@@ -316,7 +325,13 @@ void UMP_CAN_service(void)
saved = __disable_interrupts();
tag = ump_can_tag; address = ump_can_address; value = ump_can_value;
ump_can_pending = 0;
ump_can_length = ump_can_prepare(tag & 0xFF, address, value, ump_can_reply, &error);
if (address >= 0x1300U && address < 0x1308U)
ump_can_length = Load_protection_prepare(tag & 0xFF, address, value, ump_can_reply, &error);
#if EEPROM_M95M02
else if (gas167_contains(address))
ump_can_length = gas167_prepare(tag & 0xFF, address, value, 1, ump_can_reply, &error);
#endif
else ump_can_length = ump_can_prepare(tag & 0xFF, address, value, ump_can_reply, &error);
ump_can_reply_tag = (tag & 0xFF00) | error;
ump_can_index = 0;
__restore_interrupts(saved);

63
Source/Internal/gas167.c Normal file
View File

@@ -0,0 +1,63 @@
#include "gas167.h"
#if EEPROM_M95M02
#include "package.h"
extern int modbus[];
extern int Desk;
extern void ump_log_rs(void);
static const uint16_t gas167_info[GAS167_INFO_WORDS] = {
0x4741U, 0x5331U, GAS167_PROFILE_VERSION, 167U,
GAS167_REG_BASE, GAS167_REG_COUNT, GAS167_CAPABILITIES, 1U
};
int gas167_contains(uint16_t address)
{
return (uint16_t)(address - GAS167_REG_BASE) < GAS167_REG_COUNT ||
(uint16_t)(address - GAS167_INFO_BASE) < GAS167_INFO_WORDS;
}
static unsigned gas167_write(uint16_t offset, uint16_t value)
{
/* Match the existing native Modbus write policy. These status words
* cannot be overwritten and an invalid timeout cannot bypass protection. */
if (Desk == dsk_LOAD && (offset == 0x7CU || offset == 0x7DU)) return 2;
if (Desk == dsk_LOAD && offset == 0x52U &&
(value == 0U || value > 60000U)) return 3;
/* C28x int is one register word; reads always expose the raw uint16_t. */
modbus[offset] = (int)value;
if (offset == 127U) ump_log_rs();
return 0;
}
unsigned gas167_prepare(unsigned function, uint16_t address, uint16_t value,
int can, uint16_t *reply, unsigned *error)
{
uint16_t offset;
unsigned i, available;
int info;
if (!reply || !error) return 0;
*error = 0;
info = (uint16_t)(address - GAS167_INFO_BASE) < GAS167_INFO_WORDS;
offset = (uint16_t)(address - (info ? GAS167_INFO_BASE : GAS167_REG_BASE));
available = info ? GAS167_INFO_WORDS : GAS167_REG_COUNT;
if (function != 3U && function != 6U) *error = 1;
else if (offset >= available) *error = 2;
else if (function == 6U) {
*error = info ? 2U : gas167_write(offset, value);
} else if (!value || value > GAS167_MAX_WORDS || (can && (value & 1U))) {
*error = 3;
} else if (value > available - offset) {
*error = 2;
} else {
/* Validate the entire block before reading. A block cannot cross a
* region boundary, wrap at 0xFFFF, or leak the hidden parameter word. */
for (i = 0; i < value; ++i)
reply[i] = info ? gas167_info[offset+i] : (uint16_t)modbus[offset+i];
return value;
}
reply[0] = address;
reply[1] = value;
return 2;
}
#endif

View File

@@ -31,17 +31,15 @@ void clear_mem()
for (i=Log.Start; i<Log.Finis; i++)
*(volatile int *)i = 0;
}
/* Два банка используют уже выделенную внешнюю RAM .logg (64000 слов).
* Активный банк кольцевой. Через 1000 мс после команды отключения он
* становится архивом, а запись продолжается во втором банке. Переключение
* занимает постоянное время: копирования 30 тысяч слов под запретом IRQ нет.
* Архив переживает разрыв RS-связи, но НЕ исчезновение питания платы. */
/* Two RAM banks: freeze after 10% of bank capacity is recorded after stop. */
static volatile unsigned int ump_state, ump_count, ump_cursor, ump_next;
static volatile unsigned int ump_period = 20, ump_divider;
static volatile unsigned long ump_time, ump_sequence;
static unsigned int ump_bank, ump_archive_bank = 1;
static unsigned int ump_archive_count, ump_archive_next, ump_generation;
static unsigned int ump_source, ump_post, ump_post_dropped;
static unsigned int ump_source, ump_post;
#define UMP_POST_POINTS (UMP_LOG_CAPACITY / 10U)
static void ump_freeze(void);
static unsigned int ump_can_header, ump_can_data[3], ump_can_count;
/* Latest PM67 sums: register 306 (index 305), register 9 (index 8). */
static unsigned int ump_pm67_voltage[2];
@@ -62,7 +60,7 @@ void ump_log_init(void)
ump_time = 0; ump_sequence = 0;
ump_bank = 0; ump_archive_bank = 1;
ump_archive_count = 0; ump_archive_next = 0; ump_generation = 0;
ump_source = 0; ump_post = 0; ump_post_dropped = 0;
ump_source = 0; ump_post = 0;
ump_can_header = 0; ump_can_count = 0;
for (i = 0; i < 3; i++) ump_can_data[i] = 0;
for (i = 0; i < 2; i++) ump_pm67_voltage[i] = 0;
@@ -95,32 +93,30 @@ static unsigned int ump_outputs(void)
static void ump_capture(unsigned int reason)
{
volatile unsigned int *p;
unsigned int i, commands;
unsigned int i, commands, inputs, outputs;
if (ump_state != 1 || Desk != dsk_LOAD || !READY) return;
if (no_write || never_write) { ump_state = 3; return; }
commands = Commands;
/* Отключение: фронт cUMPreset (бит 6) либо спад cUMPstart (бит 7).
* Повторные команды не сдвигают конец уже начатого постинтервала. */
if (!ump_post && (((commands & 0x40) && !(ump_prev_commands & 0x40)) ||
(!(commands & 0x80) && (ump_prev_commands & 0x80)))) {
/* Половину банка резервируем под постинтервал. Даже при периоде
* 1 мс или шквале CAN событие отключения не вытесняется кольцом. */
if (ump_count >= UMP_LOG_CAPACITY/2) ump_count = UMP_LOG_CAPACITY/2-1;
ump_post_dropped = 0;
ump_post = 1000;
inputs = ump_inputs(); outputs = ump_outputs();
/* Freeze only an actual running-to-stopped output transition.
* STOP is set before RES_OUT_2 is cleared in main; an interrupt can
* observe these writes separately, so require its level, not its edge.
* Repeated reset/STOP pulses while already stopped leave the archive. */
if (!ump_post && (ump_prev_outputs & 4U) && !(outputs & 4U) &&
(outputs & 2U)) {
/* Keep 90% including the trigger; reserve 10% for later records. */
if (ump_count >= UMP_LOG_CAPACITY - UMP_POST_POINTS)
ump_count = UMP_LOG_CAPACITY - UMP_POST_POINTS - 1;
ump_post = UMP_POST_POINTS;
reason |= 0x100;
}
/* Последнее место оставляем для снимка конца постинтервала. При
* переполнении честно отмечаем усечение битом 0x200 в конечной записи. */
if (ump_post && ump_count >= UMP_LOG_CAPACITY-1) {
ump_post_dropped = 1;
return;
}
if (reason == 5 && ump_post_dropped) reason |= 0x200;
ump_prev_commands = commands; ump_prev_inputs = inputs; ump_prev_outputs = outputs;
/* Every stored point counts, including CAN and I/O, except the trigger. */
if (ump_post && !(reason & 0x100) && --ump_post == 0) reason = 5;
p = logs_block + ((unsigned long)ump_bank * UMP_LOG_CAPACITY + ump_next) * UMP_LOG_WORDS;
p[0] = (unsigned int)ump_time; p[1] = (unsigned int)(ump_time >> 16);
p[2] = reason; p[3] = commands;
p[4] = ump_inputs(); p[5] = ump_outputs();
p[4] = inputs; p[5] = outputs;
p[6] = (unsigned int)IMPowse; p[7] = (unsigned int)WAKEpowse;
p[8] = modbus[0x7B]; p[9] = modbus[0x1C];
for (i = 0; i < 4; i++) { p[10+i] = sens_data[i]; p[14+i] = modbus[i]; }
@@ -136,11 +132,11 @@ static void ump_capture(unsigned int reason)
p[33] = ump_dac_go10; p[34] = ump_dac_stop10; p[35] = ump_dac_position;
for (i = 0; i < 4; i++) p[36+i] = ADC_table[i];
p[40] = ump_pm67_voltage[0]; p[41] = ump_pm67_voltage[1];
ump_prev_commands = commands; ump_prev_inputs = p[4]; ump_prev_outputs = p[5];
ump_next++;
if (ump_next == UMP_LOG_CAPACITY) ump_next = 0;
if (ump_count < UMP_LOG_CAPACITY) ump_count++;
ump_sequence++;
if (reason == 5) ump_freeze();
}
/* Çàâåðø¸ííûé áàíê áîëüøå íå èçìåíÿåòñÿ äî ñëåäóþùåãî îòêëþ÷åíèÿ.
@@ -160,10 +156,6 @@ void ump_log_tick(void)
if (ump_state == 1) {
ump_time++;
if (++ump_divider >= ump_period) { ump_divider = 0; ump_capture(1); }
if (ump_post && --ump_post == 0) {
ump_capture(5);
ump_freeze();
}
}
__restore_interrupts(saved);
}
@@ -224,6 +216,7 @@ int ump_log_write(unsigned int address, unsigned int value)
ump_count = 0; ump_next = 0; ump_cursor = 0; ump_divider = 0; ump_post = 0;
ump_source = 0;
ump_prev_commands = Commands;
ump_prev_inputs = ump_inputs(); ump_prev_outputs = ump_outputs();
no_write = 0; never_write = 0;
ump_state = 1;
ump_capture(1);
@@ -274,7 +267,11 @@ unsigned int ump_log_read(unsigned int address)
case UMP_LOG_BASE+11: return (unsigned int)READY_FREQ;
case UMP_LOG_BASE+12: return ump_generation;
case UMP_LOG_BASE+13: return ump_archive_count;
case UMP_LOG_BASE+14: return ump_post;
/* Preserve the legacy millisecond status field as an upper estimate.
* Additional event records may finish the post interval sooner. */
case UMP_LOG_BASE+14:
first = (unsigned long)ump_post * ump_period;
return first > 65535UL ? 65535U : (unsigned int)first;
case UMP_LOG_BASE+15: return ump_state;
}
if (address >= UMP_LOG_DATA && address < UMP_LOG_DATA+120 && (ump_source || ump_state != 1)) {

View File

@@ -39,8 +39,6 @@ void main()
{
int i,j,mask;
static unsigned int cancount[2], cancell[2], rscount;
static int preCstart=1,preCstop=1;
int umpstart,umpstop;
RS_DATA * rs;
int presbyt=0;
@@ -83,6 +81,7 @@ void main()
clear_mem();
ump_log_init();
Load_control_init(); /* Explicit initialization before IRQ. */
set_cntrl_addr(Mode,16);
timer_Init();
EnableInterrupts();
@@ -178,27 +177,8 @@ void main()
presbyt = bTermoCal;
}
if(Desk==dsk_LOAD)
{
umpstart = cUMPstart;
umpstop = cUMPreset;
if(umpstart & !preCstart)
{
set_START_DPTB(); set_RES_OUT_2();
IMPowse = READY_FREQ;
}
preCstart = umpstart;
if(umpstop & !preCstop)
{
set_STOP_DPTB(); clear_RES_OUT_2();
IMPowse = READY_FREQ;
}
preCstop = umpstop;
if(!IMPowse) { clear_START_DPTB(); clear_STOP_DPTB(); }
}
/* Timer1 owns START/STOP/permission and the DAC ramp together.
* Main must not re-enable the output during a protected reload. */
/* Ñíèìîê ïîñëå îáíîâëåíèÿ âõîäîâ è ôîðìèðîâàíèÿ ïóñêîâûõ âûõîäîâ. */
ump_log_io();

View File

@@ -417,7 +417,7 @@ void Init_packMask()
if(Desk==dsk_LOAD)
{ Maska[m_SLOW][7]|= 0x0030; // Êàëèáðîâêà ÖÀÏ
Maska[m_SLOW][5]|= 0x0003; // Íà÷àëî è êîíåö çàðàäà
Maska[m_FAST][7]|= 0xE000; // Дублировать команды
Maska[m_FAST][7]|= 0xF000; /* Protection status 0x7C + attempts 0x7D. */ // Äóáëèðîâàòü êîìàíäû
Maska[m_FAST][1]|= 0x3000; // Óïðàâëàþùèé òîê ÑÈÔÓ
} }

View File

@@ -1,3 +1,5 @@
#include "eeprom_config.h"
#include "include/balsam167_eeprom_block.h"
#include "DSP2833x_Device.h" // DSP2833x Headerfile Include File
#include "package.h"
#include "RS485.h"
@@ -18,7 +20,13 @@
#include "caliber.h"
#include "log_to_mem.h"
#include "DAC.h"
#include "ump_tms28335.h"
#include "gas167.h"
#include "include/balsam167_eeprom_layout.h"
#if (ANSWER_LEN + 1) != BALSAM167_EEPROM_PARAM_WORDS
#error EEPROM_parameter_reservation_must_match_ANSWER_LEN
#endif
int modbus[ANSWER_LEN+1];
unsigned int param[ANSWER_LEN+1];
@@ -126,6 +134,7 @@ void Default_params()
// Ïðèøëî âðåìà!
DAC_go = 6; // íà÷àëî çàðàäà
LOAD_CAN_TIMEOUT = 2000; /* ms without fresh ZPT: freeze ramp. */
DAC_stop= 16; // êîíåö çàðàäà
}
@@ -141,7 +150,7 @@ void Load_params()
{
unsigned int i,crc;
Seeprom_read(0x3FFF-(ANSWER_LEN+1), param, (ANSWER_LEN+1)*2);
Seeprom_read(BALSAM167_EEPROM_PARAMS_START_WORD, param, (ANSWER_LEN+1)*2);
crc = get_crc16(param,ANSWER_LEN);
if( (crc==param[ANSWER_LEN]) &&
(crc !=0xFFFF) &&
@@ -174,7 +183,7 @@ void Save_params()
if(dif)
{
param[ANSWER_LEN] = get_crc16(param,ANSWER_LEN);
Seeprom_write(0x3FFF-(ANSWER_LEN+1),param,(ANSWER_LEN+1)*2);
Seeprom_write(BALSAM167_EEPROM_PARAMS_START_WORD,param,(ANSWER_LEN+1)*2);
} }
/***************************************************************/
@@ -206,9 +215,39 @@ static int receive_ump(RS_DATA *rs_arr)
address = (rs_arr->RS_Header[2] << 8) | rs_arr->RS_Header[3];
if (address < UMP_BASE) return 0;
value = (rs_arr->RS_Header[4] << 8) | rs_arr->RS_Header[5];
/* SPI driver advances in Timer2 ISR: NEVER mask interrupts here.
* Reuse the existing snapshot buffer; the main loop owns this transaction.
* The shared validator rejects writes, wraparound and out-of-device reads. */
if (address >= 0x1300U && address < 0x1308U) {
count = Load_protection_prepare(rs_arr->RS_Header[1], address, value, words, &error);
} else if (address >= BALSAM167_EEPROM_RTU_BASE) {
error = 2;
#if EEPROM_M95M02
/* Exception 3 prevents old AT-profile clients from falling back to
* legacy PEEK after a profile mismatch. No SPI transaction is issued. */
if (!(value & BALSAM167_EEPROM_M95_MARKER)) error = 3;
#endif
count = 0;
if (balsam167_eeprom_block_valid(rs_arr->RS_Header[1], address, value)) {
#if EEPROM_M95M02
count = value & 255U;
Seeprom_read(balsam167_eeprom_block_word(address, value), words, count * 2);
#else
count = value;
Seeprom_read(address - BALSAM167_EEPROM_RTU_BASE, words, count * 2);
#endif
error = 0;
}
} else {
saved = __disable_interrupts();
#if EEPROM_M95M02
if (gas167_contains(address))
count = gas167_prepare(rs_arr->RS_Header[1], address, value, 0, words, &error);
else
#endif
count = ump_tms28335_capture(rs_arr->RS_Header[1], address, value, words, &error);
__restore_interrupts(saved);
}
length = ump_rtu_encode(CNTRL_ADDR, rs_arr->RS_Header[1], error,
words, count, rs_arr->buffer, MAX_SEND_LENGTH);
if (length) {
@@ -289,6 +328,13 @@ void ReceiveCommandModbus6(RS_DATA *rs_arr)
// ïîëó÷èëè ñëîâî äàííûõ
Data_MB = (rs_arr->RS_Header[4] << 8) | rs_arr->RS_Header[5];
if (Desk == dsk_LOAD && (Address_MB == 0x7CU || Address_MB == 0x7DU)) {
modbus_exception(rs_arr, CMD_MODBUS_6, 2); return;
}
if (Desk == dsk_LOAD && Address_MB == 0x52U &&
(Data_MB == 0U || Data_MB > 60000U)) {
modbus_exception(rs_arr, CMD_MODBUS_6, 3); return;
}
if (Address_MB < ANSWER_LEN) {
Modbus[Address_MB].all = Data_MB;
if (Address_MB == 127) ump_log_rs();

View File

@@ -1,3 +1,7 @@
/* Build the canonical sources directly; do not copy their implementations. */
#include "../firmware-info/src/firmware_info_static.c"
/* Bind only this PM35 application's identity. The shared transport still owns
* range checks, read-only access, CRC and CAN fragment packing. */
#define UMP_FIRMWARE_INFO_READ firmware_info_static_read
#include "src/ump_protocol.c"
#include "ports/tms320f28335/ump_tms28335.c"

View File

@@ -19,12 +19,18 @@ int ccc=0;
/******* SPI bus Serial EEPROM driver Initialization routine ********/
/********************************************************************/
void Seeprom_write( unsigned int adres,
void Seeprom_write( SE2P_SIZE adres,
unsigned int buf[],
unsigned int size)
SE2P_SIZE size)
{
unsigned int len;
#if EEPROM_M95M02
/* Bound physical accesses before byte-to-word rounding. Legacy 32-KiB
* callers retain their compact driver; EXTEND is checked independently. */
if (adres >= PROM_LEN || !size || size > (PROM_LEN-adres)*2) return;
#endif
CpuTimer2.InterruptCount=0;
CpuTimer2Regs.TCR.all = 0x4020; // Use write-only instruction to set TSS bit = 0
@@ -50,12 +56,18 @@ void Seeprom_write( unsigned int adres,
CpuTimer2Regs.TCR.all = 0x4010; // Use write-only instruction to set TSS bit = 1
}
void Seeprom_read( unsigned int adres,
void Seeprom_read( SE2P_SIZE adres,
unsigned int buf[],
unsigned int size)
SE2P_SIZE size)
{
unsigned int len;
#if EEPROM_M95M02
/* Bound physical accesses before byte-to-word rounding. Legacy 32-KiB
* callers retain their compact driver; EXTEND is checked independently. */
if (adres >= PROM_LEN || !size || size > (PROM_LEN-adres)*2) return;
#endif
CpuTimer2.InterruptCount=0;
CpuTimer2Regs.TCR.all = 0x4020; // Use write-only instruction to set TSS bit = 0
@@ -244,6 +256,15 @@ void SPISE2P_DRV_tick(SPISE2P_DRV *eeprom)
case 5:
/* Send Address */
#if EEPROM_M95M02
SpiaRegs.SPICCR.all=SPISE2P_TFR8BIT;
SpiaRegs.SPITXBUF=(unsigned int)(eeprom->msgPtr->se2pAddr >> 16) << 8;
step=20;
break;
case 20:
if (!SpiaRegs.SPISTS.bit.INT_FLAG) break;
dummy=SpiaRegs.SPIRXBUF;
#endif
#if(SPISE2P_ADDR_WIDTH==SIXTEEN_BIT)
SpiaRegs.SPICCR.all= SPISE2P_TFR16BIT;
SpiaRegs.SPITXBUF=eeprom->msgPtr->se2pAddr;
@@ -374,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

View File

@@ -3,3 +3,4 @@
/.launches/
/targetConfigs/
/project.log
/Release/

View File

@@ -190,6 +190,26 @@
<type>1</type>
<locationURI>SRC_ROOT1/Source/External/v120/DSP2833x_headers/source/DSP2833x_GlobalVariableDefs.c</locationURI>
</link>
<link>
<name>Source/Internal/Include</name>
<type>2</type>
<locationURI>SRC_ROOT1/Source/Internal/Include</locationURI>
</link>
<link>
<name>Source/External/v120/DSP2833x_headers/include</name>
<type>2</type>
<locationURI>SRC_ROOT1/Source/External/v120/DSP2833x_headers/include</locationURI>
</link>
<link>
<name>Source/External/v120/DSP2833x_common/include</name>
<type>2</type>
<locationURI>SRC_ROOT1/Source/External/v120/DSP2833x_common/include</locationURI>
</link>
<link>
<name>Source/Internal/gas167.c</name>
<type>1</type>
<locationURI>SRC_ROOT1/Source/Internal/gas167.c</locationURI>
</link>
</linkedResources>
<variableList>
<variable>

View File

@@ -1,4 +0,0 @@
ccsVersionValidationPolicy=warning
compilerVersionValidationPolicy=flexible
eclipse.preferences.version=1
productVersionsValidationPolicy=flexible

View File

@@ -1,5 +1,40 @@
## Номер выпуска
Меняйте `FIRMWARE_VERSION_MAJOR`, `FIRMWARE_VERSION_MINOR` и
`FIRMWARE_VERSION_PATCH` в `firmware_version.h` в корне проекта CCS.
Например, `0U`, `1U`, `2U` задают версию 0.1.2. Сохраните файл и
выполните Rebuild. После обновления настроек закройте/откройте проект и
выполните Clean/Rebuild.
Debug и Release запускают `prepare_identity.py`: он читает номер из заголовка
и передаёт его общему генератору. `firmware_identity.json` содержит только
product/target; номер выпуска в нём редактировать не нужно.
`${BuildDirectory}/generated/firmware_identity.h` и JSON-паспорт создаются
автоматически; вручную их не меняйте. Диапазоны: major/minor 0..255,
patch 0..999. Неверный номер останавливает генерацию.
# Результаты сборки
Версия 0.1.1 возвращает паспорт прошивки и блочное чтение EEPROM поверх
подтверждённой на плате базы `c6c9128`. COM_1 остаётся на 115200; исходники
`main.c`, `RS485.c`, `RS485.h` и настройки PLL не менялись относительно этой базы.
Не используйте 250000/256000 для проверки этой сборки. FIFO остаётся включённым,
как в рабочей базе; диагностический вариант без FIFO здесь не используется.
Блочное чтение, проверка размера и host-тесты проверяются программно;
после загрузки сначала проверьте чтение версии 0.1.1, затем 256 байт EEPROM.
Debug post-build CCS12 также копирует проверенный BIN и сопутствующие
`.manifest.json`/`.sha256` в
`../newProject/SETGUI/..periph_28335/firmware/periph28335/bal`.
Папка создаётся автоматически; имя BIN с Git commit сохраняется.
Копирование выполняет `copy_bin_to_setgui.ps1` после успешной проверки образа
и создания паспорта. Пути определяются относительно репозитория, не cwd CCS.
Для ручного копирования: `./copy_bin_to_setgui.ps1 -Path ./Bin/<имя>.bin`.
Версия работающей прошивки, SHA-256 BIN и паспорт сборки описаны в
[инструкции для 167](../Doc/FIRMWARE_IDENTITY.md). Перед компиляцией запускается
`preBuildStep.bat`; требуются Python 3.10+ и Git в PATH CCS. Debug post-build
создаёт рядом с BIN файлы `.manifest.json` и `.sha256`.
Для сборки требуется общий UMP-протокол из соседнего репозитория:
`../newProject/SETGUI/third_party/templates/c/set-protocol` (от корня этого
репозитория). Пути к заголовкам заданы в `.cproject` для Debug и Release;
@@ -14,3 +49,7 @@
BIN создаётся скриптом `build_bin.bat`; Git должен быть доступен в `PATH`
процесса CCS. Если определить коммит не удалось, сборка завершается ошибкой.
При наличии незакоммиченных правок хеш обозначает HEAD и не описывает эти правки.
## GAS для новой EEPROM
При EEPROM_M95M02=1 доступен профиль GAS 167/v1 через Modbus и служебный CAN УМП. Карта, проверки и измеренные размеры: [GAS_167_M95M02.md](../Doc/GAS_167_M95M02.md). В сборке AT25256B GAS исключён; её загрузочный BIN не увеличивается. После обновления проекта выполнить Clean/Rebuild: новый gas167.c зарегистрирован в .project.

View File

@@ -0,0 +1,4 @@
{
"product": "Balsam-167-PM35",
"target": "TMS320F28335"
}

View File

@@ -0,0 +1,9 @@
#ifndef BALSAM_FIRMWARE_VERSION_H
#define BALSAM_FIRMWARE_VERSION_H
/* Edit these numbers before releasing firmware, then rebuild. */
#define FIRMWARE_VERSION_MAJOR 1U
#define FIRMWARE_VERSION_MINOR 1U
#define FIRMWARE_VERSION_PATCH 2U
#endif

View File

@@ -1,33 +0,0 @@
@echo off
setlocal
for %%I in ("%~dp0..") do set "SRC_ROOT=%%~fI"
:process_arg
if "%1"=="" goto end_process_arg
set name=%1
set value=
:process_arg_value
if NOT "%value%"=="" set value=%value% %2
if "%value%"=="" set value=%2
shift
if "%2"=="!" goto set_arg
if "%2"=="" goto set_arg
goto process_arg_value
:set_arg
set %name%=%value%
shift
shift
goto process_arg
:end_process_arg
pushd "%SRC_ROOT%\Bin" || exit /b 1
hex2000.exe UKSSTMS320F28335.out -boot -sci8 -map UKSSTMS320F28335.map -o UKSSTMS320F28335.hex -i
set "BUILD_RESULT=%errorlevel%"
popd
if not "%BUILD_RESULT%"=="0" exit /b %BUILD_RESULT%
call "%SRC_ROOT%\build_bin.bat"
exit /b %errorlevel%

View File

@@ -0,0 +1,48 @@
"""Generate build identity using the release version from firmware_version.h."""
import argparse
import importlib.util
import json
from pathlib import Path
import re
import tempfile
def read_version(header):
text = header.read_text(encoding="utf-8-sig")
text = re.sub(r"/\*.*?\*/|//[^\n]*", "", text, flags=re.S)
version = []
for part, limit in (("MAJOR", 255), ("MINOR", 255), ("PATCH", 999)):
name = "FIRMWARE_VERSION_" + part
definitions = re.findall(r"^\s*#\s*define\s+" + name + r"\b([^\n]*)", text, re.M)
if len(definitions) != 1:
raise ValueError(f"{header}: define {name} exactly once")
value = definitions[0].strip()
if not re.fullmatch(r"(?:0|[1-9][0-9]*)[uU]?", value):
raise ValueError(f"{header}: {name} must be a decimal integer")
number = int(value.rstrip("uU"))
if not 0 <= number <= limit:
raise ValueError(f"{header}: {name} must be in 0..{limit}")
version.append(number)
return version
def prepare(project, templates, output):
settings = json.loads((project / "firmware_identity.json").read_text(encoding="utf-8-sig"))
settings["version"] = read_version(project / "firmware_version.h")
spec = importlib.util.spec_from_file_location("shared_firmware_identity", templates / "c/firmware-info/tools/identity.py")
identity = importlib.util.module_from_spec(spec)
spec.loader.exec_module(identity)
# The source header owns SemVer; retain the shared generator and manifest format.
with tempfile.TemporaryDirectory(prefix="balsam-identity-") as directory:
config = Path(directory) / "config.json"
config.write_text(json.dumps(settings), encoding="utf-8")
return identity.prepare(project, templates, config, output)
if __name__ == "__main__":
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--project", type=Path, required=True)
parser.add_argument("--templates", type=Path, required=True)
parser.add_argument("--output", type=Path, required=True)
args = parser.parse_args()
print(json.dumps(prepare(args.project, args.templates, args.output)))

View File

@@ -1,20 +0,0 @@
@echo off
setlocal
set "SRC_ROOT=%~dp0"
set "GIT_COMMIT="
for /f "delims=" %%H in ('git -C "%SRC_ROOT%." rev-parse --short^=8 HEAD') do set "GIT_COMMIT=%%H"
if not defined GIT_COMMIT (
echo ERROR: Cannot determine the Git commit. Make Git available in PATH. 1>&2
exit /b 1
)
pushd "%SRC_ROOT%Bin" || exit /b 1
hex2000.exe UKSSTMS320F28335.out -boot -sci8 -map UKSSTMS320F28335.map -o UKSSTMS320F28335_%GIT_COMMIT%.bin.pending -b
set "BUILD_RESULT=%errorlevel%"
if not "%BUILD_RESULT%"=="0" goto finish
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%SRC_ROOT%check_firmware.ps1" -Path "UKSSTMS320F28335_%GIT_COMMIT%.bin.pending" -PublishPath "UKSSTMS320F28335_%GIT_COMMIT%.bin"
set "BUILD_RESULT=%errorlevel%"
:finish
popd
exit /b %BUILD_RESULT%

View File

@@ -1,53 +0,0 @@
param(
[string]$TemplatesRoot = "$PSScriptRoot/../newProject/SETGUI/third_party/templates",
[string]$CompilerRoot = 'C:/ti/ccs1230/ccs/tools/compiler/ti-cgt-c2000_22.6.0.LTS',
[string]$OutputDirectory = "$PSScriptRoot/Bin/LoggerEepromFix",
[switch]$OptimizeForSize,
[ValidateSet(0,1)][int]$Rs232Fifo = 1,
[string]$ImageName = "UKSSTMS320F28335_logger_eeprom_fix"
)
$ErrorActionPreference = 'Stop'
$protocol = Join-Path (Resolve-Path -LiteralPath $TemplatesRoot).Path 'c/set-protocol'
foreach ($required in @('src/ump_protocol.c', 'ports/tms320f28335/ump_tms28335.c')) {
if (!(Test-Path -LiteralPath (Join-Path $protocol $required))) {
throw "Missing $required in $protocol. Use templates with the UMP v3 port."
}
}
$output = $OutputDirectory
$objects = Join-Path $output 'obj'
New-Item -ItemType Directory -Force -Path $objects | Out-Null
$includes = @('Source/Internal/Include', 'Source/External/v120/DSP2833x_headers/include',
'Source/External/v120/DSP2833x_common/include') | ForEach-Object { "--include_path=$PSScriptRoot/$_" }
$includes += @("--include_path=$CompilerRoot/include", "--include_path=$protocol",
"--include_path=$protocol/ports/tms320f28335")
# Match the CCS Debug configuration used for 34a95bc5.
$flags = @('-v28', '-ml', '-mt', '--float_support=fpu32', '-O1', '--define=LARGE_MODEL',
'--define=_DEBUG', '--symdebug:dwarf', '--abi=coffabi', '--opt_for_speed=3')
$flags += "--define=RS232_USE_FIFO=$Rs232Fifo"
if ($OptimizeForSize) {
$flags = @($flags | Where-Object { $_ -ne '-O1' -and $_ -ne '--opt_for_speed=3' })
$flags += @('-O2', '--opt_for_speed=0')
}
$external = @('DSP2833x_ADC_cal.asm', 'DSP2833x_Adc.c', 'DSP2833x_CpuTimers.c',
'DSP2833x_PieCtrl.c', 'DSP2833x_SWPrioritizedDefaultIsr.c', 'DSP2833x_SWPrioritizedPieVect.c',
'DSP2833x_SysCtrl.c', 'DSP2833x_Xintf.c', 'DSP2833x_usDelay.asm')
$sources = @(Get-ChildItem -LiteralPath "$PSScriptRoot/Source/Internal" -Filter '*.c' | Sort-Object Name | ForEach-Object FullName)
$sources += $external | ForEach-Object { "$PSScriptRoot/Source/External/v120/DSP2833x_common/source/$_" }
$sources += "$PSScriptRoot/Source/External/v120/DSP2833x_headers/source/DSP2833x_GlobalVariableDefs.c"
$objectFiles = @()
foreach ($source in $sources) {
& "$CompilerRoot/bin/cl2000.exe" @flags @includes --compile_only "--obj_directory=$objects" $source
if ($LASTEXITCODE -ne 0) { throw "Compilation failed: $source" }
$objectFiles += Join-Path $objects ([IO.Path]::GetFileNameWithoutExtension($source) + '.obj')
}
$name = $ImageName
& "$CompilerRoot/bin/cl2000.exe" @flags -z "--map_file=$output/$name.map" --stack_size=0x3f0 --warn_sections `
"--search_path=$CompilerRoot/lib" --reread_libs --entry_point=_c_int00 --rom_model `
"--output_file=$output/$name.out" @objectFiles "$PSScriptRoot/F28335.cmd" `
"$PSScriptRoot/Source/External/v120/DSP2833x_headers/cmd/DSP2833x_Headers_nonBIOS.cmd" `
"$PSScriptRoot/Libraries/rts2800_fpu32.lib"
if ($LASTEXITCODE -ne 0) { throw 'Link failed' }
& "$CompilerRoot/bin/hex2000.exe" "$output/$name.out" -boot -sci8 -map "$output/${name}_hex.map" -o "$output/$name.bin.pending" -b
if ($LASTEXITCODE -ne 0) { throw 'Binary conversion failed' }
& "$PSScriptRoot/check_firmware.ps1" -Path "$output/$name.bin.pending" -PublishPath "$output/$name.bin"
Write-Output "Firmware: $output/$name.bin"

View File

@@ -1,36 +0,0 @@
param(
[Parameter(Mandatory=$true)][string]$Path,
[string]$PublishPath
)
$ErrorActionPreference = 'Stop'
# Load_params/Save_params use word address 0x3FFF-(ANSWER_LEN+1).
# Seeprom_write converts that address to wire bytes (WORD_LEN=2).
$header = Get-Content -Raw -LiteralPath "$PSScriptRoot/Source/Internal/Include/message.h"
if ($header -notmatch '(?m)^#define\s+ANSWER_LEN\s+(0x[0-9a-fA-F]+|[0-9]+)') {
throw 'Cannot determine the EEPROM parameter reservation from ANSWER_LEN'
}
$registerCount = if ($Matches[1].StartsWith('0x')) {
[Convert]::ToInt32($Matches[1].Substring(2), 16)
} else { [int]$Matches[1] }
$limit = (0x3FFF - ($registerCount + 1)) * 2
$bytes = [IO.File]::ReadAllBytes((Resolve-Path -LiteralPath $Path).Path)
if ($bytes.Length -gt $limit) {
throw "Firmware size $($bytes.Length) exceeds EEPROM firmware area $limit bytes; overlaps saved parameters by $($bytes.Length - $limit) bytes."
}
if ($bytes.Length -lt 30 -or [BitConverter]::ToUInt16($bytes, 0) -ne 0x08AA) {
throw 'Expected a C2000 SCI8 boot image'
}
$offset = 22
$blocks = 0
while ($true) {
if ($offset + 2 -gt $bytes.Length) { throw 'Missing boot table terminator' }
$words = [BitConverter]::ToUInt16($bytes, $offset)
$offset += 2
if (!$words) { break }
$offset += 4 + 2 * $words
if ($offset -gt $bytes.Length) { throw 'Truncated boot table block' }
$blocks++
}
if (!$blocks -or $offset -ne $bytes.Length) { throw 'Invalid boot table length' }
if ($PublishPath) { Move-Item -LiteralPath $Path -Destination $PublishPath -Force }
Write-Output "Verified SCI8 image: $($bytes.Length) bytes, $blocks blocks, $($limit - $bytes.Length) bytes before saved parameters."

View File

@@ -0,0 +1,21 @@
"""Check SPI wire address widths for both production EEPROM configurations."""
from pathlib import Path
import subprocess
import os
import shutil
import tempfile
root = Path(__file__).resolve().parents[2]
protocol = root.parent / 'newProject/SETGUI/third_party/templates/c/set-protocol'
compiler = os.environ.get('CC') or shutil.which('gcc') or 'C:/msys64/mingw64/bin/gcc.exe'
os.environ['PATH'] = str(Path(compiler).resolve().parent) + os.pathsep + os.environ['PATH']
with tempfile.TemporaryDirectory() as folder:
for profile in (0, 1):
exe = Path(folder) / f'spi{profile}.exe'
subprocess.run([compiler, '-std=c99',
f'-DEEPROM_M95M02={profile}', '-Itests/eeprom_profiles/stubs',
'-ISource/Internal/Include', '-I' + str(protocol),
'tests/eeprom_profiles/spi_test.c', '-o', str(exe)],
cwd=root, check=True)
subprocess.run([str(exe)], check=True, timeout=10)
print(f'PASS SPI profile {profile}')

View File

@@ -0,0 +1,47 @@
/* Exercise the production Timer2 SPI state machine; no hardware writes. */
#include <assert.h>
#include "../../Source/Internal/spise2p.c"
static unsigned trace[300], widths[300], used;
static void transfer(unsigned long address, int write)
{
unsigned buf[2]={0x1234,0x5678}, i;
SE2P_DATA message={buf,2,address};
used=0;
if(write) spiSe2pWrite(&se2p,&message); else spiSe2pRead(&se2p,&message);
for(i=0;i<100 && !spiSe2pFree(&se2p);i++) {
SpiaRegs.SPITXBUF=0xdead;
SpiaRegs.SPIRXBUF=0; /* WIP clear for write, deterministic read data. */
SpiaRegs.SPISTS.bit.INT_FLAG=1;
SPISE2P_DRV_tick(&se2p);
if(SpiaRegs.SPITXBUF!=0xdead) {
assert(GpioDataRegs.GPADAT.bit.GPIO19==0);
trace[used]=SpiaRegs.SPITXBUF;
widths[used++]=(SpiaRegs.SPICCR.all&15)+1;
}
}
assert(spiSe2pFree(&se2p) && GpioDataRegs.GPADAT.bit.GPIO19==1);
i=0;
if(write) { assert(trace[i]==0x0600 && widths[i++]==8); }
assert(trace[i]==(write ? 0x0200 : 0x0300) && widths[i++]==8);
#if EEPROM_M95M02
assert(trace[i]==((address>>16)<<8) && widths[i++]==8);
#endif
assert(trace[i]==(address&65535) && widths[i++]==16);
assert(trace[i]==(write ? 0x1234 : 0) && widths[i++]==16);
assert(trace[i]==(write ? 0x5678 : 0) && widths[i++]==16);
if(write) { assert(trace[i]==0x0500 && widths[i++]==8); assert(trace[i++]==0); }
assert(i==used);
}
int main(void)
{
SPISE2P_DRV_init(&se2p);
transfer(0x7ffc,0); transfer(0x7ffc,1);
#if EEPROM_M95M02
assert(BALSAM167_EEPROM_FIRMWARE_BYTES==261884UL);
transfer(0xfffe,0); transfer(0x10000,0);
transfer(0x20000,1); transfer(0x3fffc,0); transfer(0x3fffc,1);
#else
assert(BALSAM167_EEPROM_FIRMWARE_BYTES==32508);
#endif
return 0;
}

View 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

View File

@@ -0,0 +1,42 @@
"""Independent test oracle: does this built SCI8 file contain its manifest identity?
This is a build test, not a second production protocol codec. The 12-word vector
is deliberately constructed independently of the C implementation to detect a
stale compilation unit, incorrect byte order or a mismatched pre-build JSON.
Usage: python tests/firmware_identity/check_build.py path/to/firmware.bin
"""
from datetime import datetime
import hashlib
import json
from pathlib import Path
import struct
import sys
image = Path(sys.argv[1])
data = image.read_bytes()
meta = json.loads(image.with_suffix(image.suffix + '.manifest.json').read_text())
assert hashlib.sha256(data).hexdigest() == meta['sha256'], 'Wrong file hash'
assert len(data) == meta['size_bytes'], 'Wrong image size'
stamp = datetime.fromisoformat(meta['built_at_utc'])
build_id = meta['build_id']
words = [1, *meta['version'], stamp.year, stamp.month * 256 + stamp.day,
stamp.hour * 256 + stamp.minute, stamp.second]
words += [ord(build_id[i]) * 256 + ord(build_id[i+1]) for i in range(0, 8, 2)]
expected = struct.pack('<12H', *words)
assert data[:2] == b'\xaa\x08', 'Not SCI8'
offset = 22
found = 0
while True:
count, = struct.unpack_from('<H', data, offset)
offset += 2
if not count:
break
offset += 4 # destination address, not part of the loaded section payload
end = offset + count * 2
assert end <= len(data), 'Truncated boot block'
block = data[offset:end]
found += sum(block[i:i+24] == expected for i in range(0, len(block)-23, 2))
offset = end
assert offset == len(data), 'Trailing data'
assert found == 1, f'Expected one embedded identity, found {found}'
print(f'PASS: SCI8 contains manifest identity {meta["version"]}, {build_id}, {stamp.isoformat()}')

View File

@@ -0,0 +1,33 @@
/* Test the actual 167 binding, not just a fake protocol backend. Build against
* the generated header from the firmware build under test. No MCU registers. */
#include "firmware_identity.h"
#include "firmware_info_static.h"
#include "ump_tms28335.h"
#include <assert.h>
static unsigned logger_reads, logger_writes;
unsigned int ump_log_read(unsigned int address)
{ (void)address; ++logger_reads; return 0x554dU; }
int ump_log_write(unsigned int address, unsigned int value)
{ (void)address; (void)value; ++logger_writes; return 1; }
int main(void)
{
uint16_t words[UMP_MAX_WORDS];
ump_octet wire[UMP_RTU_CAPACITY];
unsigned error, i;
assert(ump_tms28335_prepare(3, UMP_FIRMWARE_INFO, 12, words, &error) == 12 && !error);
assert(words[0] == 1 && words[1] == FIRMWARE_VERSION_MAJOR);
assert(words[2] == FIRMWARE_VERSION_MINOR && words[3] == FIRMWARE_VERSION_PATCH);
assert(words[4] == FI_YEAR && words[5] == FI_PAIR(FI_MONTH, FI_DAY));
assert(words[8] == FI_PAIR(FI_ID_0, FI_ID_1));
assert(logger_reads == 0);
assert(ump_tms28335_rtu(1,3,UMP_FIRMWARE_INFO,12,wire,UMP_RTU_CAPACITY) == 29);
for (i=0; i<12; ++i) assert(((wire[3+i*2]<<8) | wire[4+i*2]) == words[i]);
assert(ump_crc(wire,29) == 0);
ump_tms28335_prepare(6,UMP_FIRMWARE_INFO,123,words,&error);
assert(error == 2 && logger_writes == 0);
/* Firmware reads must not steal the existing logger window. */
assert(ump_tms28335_prepare(3,UMP_BASE,16,words,&error) == 16 && !error);
assert(logger_reads == 16 && words[0] == 0x554dU);
return 0;
}

View File

@@ -0,0 +1,48 @@
"""Validate the PM35 RAM-load SCI8 image against the selected EEPROM profile."""
import argparse
from pathlib import Path
import struct
def check_image(path, eeprom):
data = path.read_bytes()
limit = {"AT25256B": 32508, "M95M02": 261884}[eeprom]
if not 24 <= len(data) <= limit:
raise ValueError(f"Image size {len(data)} outside 24..{limit}")
if struct.unpack_from("<H", data)[0] != 0x08AA:
raise ValueError("Not a SCI8 boot image")
high, low = struct.unpack_from("<HH", data, 18)
entry = (high << 16) | low
position, blocks = 22, []
while True:
if position + 2 > len(data):
raise ValueError("Missing boot terminator")
words = struct.unpack_from("<H", data, position)[0]
position += 2
if not words:
if position != len(data):
raise ValueError("Trailing bytes after boot terminator")
break
if position + 4 + words * 2 > len(data):
raise ValueError("Truncated boot block")
high, low = struct.unpack_from("<HH", data, position)
address = (high << 16) | low
end = address + words
if not 0x8000 <= address < end <= 0xD000:
raise ValueError(f"Boot destination {address:#x}..{end:#x} outside code RAM")
if any(address < old_end and old_start < end for old_start, old_end in blocks):
raise ValueError("Overlapping boot blocks")
blocks.append((address, end))
position += 4 + words * 2
if not any(start <= entry < end for start, end in blocks):
raise ValueError("Entry point not loaded by the boot table")
return len(data), len(blocks), limit - len(data)
if __name__ == "__main__":
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("image", type=Path)
parser.add_argument("--eeprom", choices=("AT25256B", "M95M02"), required=True)
args = parser.parse_args()
size, blocks, free = check_image(args.image, args.eeprom)
print(f"PASS SCI8 {args.eeprom}: {size} bytes, {blocks} RAM blocks, {free} bytes free")

196
tests/gas167/gas167_test.c Normal file
View File

@@ -0,0 +1,196 @@
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include "gas167.h"
#include "ump_protocol.h"
#include "ump_tms28335.h"
#include "include/balsam167_eeprom_block.h"
#define ANSWER_LEN 128U
#define MAX_SEND_LENGTH 400U
#define CNTRL_ADDR 16U
#define CMD_MODBUS_3 3U
#define CMD_MODBUS_6 6U
#define dsk_LOAD 6
#define LOBYTE(v) ((v) & 255U)
#define HIBYTE(v) (((v) >> 8) & 255U)
#define UMP_CAN_MAX_WORDS UMP_MAX_WORDS
#define ump_can_prepare ump_tms28335_prepare
typedef struct { int all; } WORDE;
#define Modbus ((WORDE *)modbus)
typedef struct {
uint16_t RS_Header[8], buffer[MAX_SEND_LENGTH];
unsigned flag_TIMEOUT_to_Send;
} RS_DATA;
int modbus[129], Desk = dsk_LOAD;
static unsigned captures, spi_reads, sent, irq_depth, legacy_reads;
static unsigned __disable_interrupts(void) {
unsigned saved = irq_depth;
++irq_depth;
return saved;
}
static void __restore_interrupts(unsigned saved) { irq_depth = saved; }
void ump_log_rs(void) { ++captures; }
unsigned int ump_log_read(unsigned int address) { ++legacy_reads; return address; }
int ump_log_write(unsigned int address, unsigned int value) { return 1; }
static unsigned get_crc_16(unsigned seed, const uint16_t *data, unsigned size) {
assert(seed == 0xFFFFU);
return ump_crc(data, size);
}
static void RS_Send(RS_DATA *rs, uint16_t *data, unsigned size) {
assert(irq_depth == 0);
sent = size;
}
static void Seeprom_read(unsigned long word, uint16_t *data, unsigned bytes) {
unsigned i;
assert(irq_depth == 0); /* The Timer2 SPI driver must retain interrupts. */
++spi_reads;
for (i = 0; i < bytes/2; ++i) data[i] = (uint16_t)(0xBEEFU+i);
}
static unsigned Load_protection_prepare(unsigned fn, unsigned a, unsigned n,
uint16_t *out, unsigned *error) {
out[0] = (uint16_t)a; out[1] = (uint16_t)n; *error = 0;
return 2;
}
struct mock_reg { uint32_t all; };
static struct {
struct mock_reg CANTA, CANAA, CANTRR, CANTRS;
} ECanaRegs;
static struct {
struct { struct mock_reg MDH, MDL; } MBOX2;
} ECanaMboxes;
static struct { unsigned long InterruptCount; } CpuTimer1;
#include "production_routes.inc"
static void request(RS_DATA *rs, unsigned fn, unsigned a, unsigned n) {
unsigned crc;
memset(rs, 0, sizeof(*rs));
rs->RS_Header[0] = CNTRL_ADDR; rs->RS_Header[1] = fn;
rs->RS_Header[2] = a >> 8; rs->RS_Header[3] = a & 255U;
rs->RS_Header[4] = n >> 8; rs->RS_Header[5] = n & 255U;
crc = ump_crc(rs->RS_Header, 6);
rs->RS_Header[6] = crc & 255U; rs->RS_Header[7] = crc >> 8;
sent = 0;
if (fn == 3) ReceiveCommandModbus3(rs);
else ReceiveCommandModbus6(rs);
assert(irq_depth == 0 && rs->flag_TIMEOUT_to_Send);
assert(ump_crc(rs->buffer, rs->buffer[1] & 128U ? 5 :
(fn == 6 ? 8 : 5+rs->buffer[2])) == 0);
}
static void can_request(unsigned fn, unsigned a, unsigned n) {
memset(&ECanaRegs, 0, sizeof(ECanaRegs));
memset(&ECanaMboxes, 0, sizeof(ECanaMboxes));
ump_can_waiting = 0; ump_can_length = 0; ump_can_index = 0;
ump_can_pending = 1; ump_can_tag = 0x0100U | fn;
ump_can_address = a; ump_can_value = n;
UMP_CAN_service();
assert(irq_depth == 0 && !ump_can_pending);
assert(ECanaRegs.CANTRS.all == 4U);
}
int main(void) {
RS_DATA rs;
unsigned i;
for (i = 0; i < 129; ++i) modbus[i] = (int)i;
modbus[128] = 0xD00D;
/* Exercise the real native handlers in both builds. */
request(&rs, 6, 1, 0xFEDC);
assert(modbus[1] == 0xFEDC && sent == 10);
request(&rs, 3, 0, 2);
assert(rs.buffer[2] == 4 && rs.buffer[5] == 0xFE && rs.buffer[6] == 0xDC);
request(&rs, 6, 0x52, 0);
assert(rs.buffer[1] == 0x86 && rs.buffer[2] == 3 && modbus[0x52] == 0x52);
request(&rs, 6, 0x7C, 9);
assert(rs.buffer[2] == 2 && modbus[0x7C] == 0x7C);
/* Existing UMP and protection routing must still work. */
request(&rs, 3, 0x1000, 2);
assert(rs.buffer[1] == 3 && rs.buffer[3] == 0x10 && legacy_reads == 2);
request(&rs, 3, 0x1300, 2);
assert(rs.buffer[1] == 3 && rs.buffer[3] == 0x13);
#if EEPROM_M95M02
{
uint16_t out[124];
unsigned error, count;
for (i = 0; i < 65536U; ++i)
assert(gas167_contains((uint16_t)i) ==
((i >= 0x5000U && i <= 0x507FU) || (i >= 0x5F00U && i <= 0x5F07U)));
assert(gas167_prepare(3, 0x5000, 124, 0, out, &error) == 124 && !error);
assert(out[1] == 0xFEDC && out[123] == 123);
count = gas167_prepare(3, 0x507F, 2, 0, out, &error);
assert(count == 2 && error == 2 && modbus[128] == 0xD00D);
assert(gas167_prepare(3, 0x5000, 0, 0, out, &error) == 2 && error == 3);
assert(gas167_prepare(3, 0x5000, 125, 0, out, &error) == 2 && error == 3);
assert(gas167_prepare(3, 0x5000, 1, 1, out, &error) == 2 && error == 3);
assert(gas167_prepare(3, 0xFFFF, 2, 0, out, &error) == 2 && error == 2);
assert(gas167_prepare(16, 0x5000, 1, 0, out, &error) == 2 && error == 1);
assert(gas167_prepare(6, 0x5080, 7, 0, out, &error) == 2 && error == 2);
assert(gas167_prepare(6, 0x5052, 60001, 0, out, &error) == 2 && error == 3);
assert(gas167_prepare(6, 0x5052, 60000, 0, out, &error) == 2 && !error);
assert(modbus[0x52] == 60000);
assert(gas167_prepare(3, 0x5000, 1, 0, 0, &error) == 0);
assert(gas167_prepare(6, 0x5000, 1, 0, out, 0) == 0 && modbus[0] == 0);
}
request(&rs, 3, 0x5000, 2);
assert(sent == 9 && rs.buffer[5] == 0xFE && rs.buffer[6] == 0xDC);
request(&rs, 3, 0x5000, 124);
assert(sent == 253 && rs.buffer[2] == 248);
request(&rs, 3, 0x5F00, 8);
assert(sent == 21 && rs.buffer[3] == 0x47 && rs.buffer[4] == 0x41);
assert(rs.buffer[5] == 0x53 && rs.buffer[6] == 0x31);
assert(rs.buffer[10] == 167 && rs.buffer[18] == 1);
request(&rs, 6, 0x5F00, 0);
assert(sent == 5 && rs.buffer[2] == 2);
request(&rs, 3, 0x5F07, 2);
assert(rs.buffer[2] == 2);
request(&rs, 6, 0x5052, 0);
assert(rs.buffer[2] == 3 && modbus[0x52] == 60000);
request(&rs, 6, 0x507D, 9);
assert(rs.buffer[2] == 2 && modbus[0x7D] == 0x7D);
request(&rs, 6, 0x507F, 0x80);
assert(sent == 8 && rs.buffer[2] == 0x50 && rs.buffer[3] == 0x7F);
assert(captures == 1 && modbus[127] == 0x80);
can_request(6, 0x5001, 0xFEDC);
assert(modbus[1] == 0xFEDC && ECanaMboxes.MBOX2.MDL.all == 0x5001FEDCUL);
assert(ECanaMboxes.MBOX2.MDH.all == 0x1F000100UL);
can_request(3, 0x5001, 2);
assert(ECanaMboxes.MBOX2.MDL.all == 0xFEDC0002UL);
can_request(3, 0x5000, 124);
assert(ump_can_length == 124 && ump_can_index == 2);
modbus[2] = 777; /* All fragments must retain the original snapshot. */
for (i = 1; i < 62; ++i) {
ECanaRegs.CANTA.all = 4;
UMP_CAN_service();
assert(ump_can_index == 2*(i+1));
if (i == 1) assert(ECanaMboxes.MBOX2.MDL.all == 0x00020003UL);
}
modbus[2] = 2;
can_request(3, 0x5000, 3);
assert(ECanaMboxes.MBOX2.MDH.all == 0x1F000103UL);
can_request(6, 0x507C, 9);
assert((ECanaMboxes.MBOX2.MDH.all & 255U) == 2 && modbus[0x7C] == 0x7C);
can_request(6, 0x507F, 0x40);
assert(captures == 2 && modbus[127] == 0x40);
can_request(6, 0x5F00, 9);
assert((ECanaMboxes.MBOX2.MDH.all & 255U) == 2);
request(&rs, 3, 0x8000, 0x8001);
#else
request(&rs, 3, 0x5000, 2);
assert(sent == 5 && rs.buffer[2] == 2);
request(&rs, 6, 0x5001, 9);
assert(rs.buffer[2] == 2 && modbus[1] == 0xFEDC);
request(&rs, 3, 0x5000, 124);
assert(sent == 5 && rs.buffer[2] == 2);
request(&rs, 3, 0x5F00, 8);
assert(rs.buffer[2] == 2);
can_request(6, 0x5001, 9);
assert((ECanaMboxes.MBOX2.MDH.all & 255U) == 2 && modbus[1] == 0xFEDC);
assert(captures == 0);
request(&rs, 3, 0x8000, 1);
#endif
assert(spi_reads == 1 && rs.buffer[3] == 0xBE && rs.buffer[4] == 0xEF);
assert(modbus[128] == 0xD00D);
printf("PASS GAS167 profile %d: production RTU/CAN routes, guards, CRC and legacy services\n", EEPROM_M95M02);
return 0;
}

51
tests/gas167/run_test.py Normal file
View File

@@ -0,0 +1,51 @@
"""Exercise the production GAS provider and extracted production RTU/CAN routes."""
import os
from pathlib import Path
import re
import shutil
import subprocess
import tempfile
root = Path(__file__).resolve().parents[2]
templates = Path(os.environ.get("TEMPLATES_ROOT", root.parent / "newProject/SETGUI/third_party/templates"))
protocol = templates / "c/set-protocol"
compiler = os.environ.get("CC") or shutil.which("gcc") or "C:/msys64/mingw64/bin/gcc.exe"
os.environ["PATH"] = str(Path(compiler).resolve().parent) + os.pathsep + os.environ["PATH"]
def function(text, name):
match = re.search(r"^(?:static )?(?:void|int) " + name + r"\(", text, re.M)
if not match:
raise ValueError(f"Production function {name} missing")
end = re.search(r"^}", text[match.start():], re.M)
if not end:
raise ValueError(f"Production function {name} incomplete")
return text[match.start():match.start() + end.end()] + "\n"
message = (root / "Source/Internal/message.c").read_text(encoding="cp1251")
ecan = (root / "Source/Internal/ecan.c").read_text(encoding="cp1251")
can_globals = ecan[ecan.index("static volatile unsigned int ump_can_pending;"):
ecan.index("static unsigned long ump_can_sent_at;") + len("static unsigned long ump_can_sent_at;")]
# On C28x unsigned int is uint16_t. Preserve that target word layout on GCC.
can_globals = can_globals.replace("static unsigned int ump_can_reply[",
"static uint16_t ump_can_reply[")
routes = can_globals + "\n" + "".join(function(message, name) for name in
("modbus_exception", "receive_ump", "ReceiveCommandModbus3", "ReceiveCommandModbus6"))
routes += function(ecan, "UMP_CAN_service")
with tempfile.TemporaryDirectory(prefix="gas167-test-") as tmp:
directory = Path(tmp)
(directory / "production_routes.inc").write_text(routes, encoding="utf-8")
for profile in (0, 1):
exe = directory / f"gas167_{profile}.exe"
subprocess.run([compiler, "-std=c99", "-Wall", "-Wextra",
"-Wno-unused-function", "-Wno-unused-parameter",
f"-DEEPROM_M95M02={profile}", "-I" + str(directory),
"-ISource/Internal/Include", "-I" + str(protocol),
"-I" + str(protocol / "include"),
"-I" + str(protocol / "ports/tms320f28335"),
"tests/gas167/gas167_test.c", "Source/Internal/gas167.c",
str(protocol / "src/ump_protocol.c"),
str(protocol / "ports/tms320f28335/ump_tms28335.c"),
"-o", str(exe)], cwd=root, check=True)
subprocess.run([str(exe)], check=True, timeout=10)

View File

@@ -0,0 +1,103 @@
#include <stdio.h>
#include <string.h>
#include "../../Source/Internal/DAC.c"
int READY = 1, Desk = 6, Mode = 5;
int modbus[128];
unsigned int test_start, test_stop, test_permission, test_tick;
unsigned int test_starts, test_last_stop_tick, test_last_start_tick, test_init_dac;
int test_bad_order;
unsigned int ump_dac_ramp, ump_dac_duration, ump_dac_go10, ump_dac_stop10, ump_dac_position;
#define CHECK(x) do { if (!(x)) { printf("FAIL line %d: %s\n", __LINE__, #x); return 1; } } while (0)
static void setup(int mode) {
memset(modbus, 0, sizeof(modbus)); Mode = mode; READY = 1;
test_start = test_stop = test_permission = test_tick = test_starts = 0;
test_bad_order = test_init_dac = 0;
Load_control_init(); Setup_DAC_time(); WAKEpowse = 0;
DAC_go = 4; DAC_stop = 16; DAC_min() = 100; DAC_max() = 3000;
}
static void tick(unsigned int n, int exchange, unsigned int volts) {
while (n--) {
if (exchange) Load_voltage_can(0x8000U | (Mode == 5 ? 305U : 8U), volts, 0, 0);
test_tick++; Load_runner();
}
}
int main(void) {
unsigned int out[8], error, count, ramp, code, stopped_at;
setup(5);
/* Boot ISR before parameters: no divide-by-zero and no GPIO start. */
Load_control_init(); Commands = 0x80; tick(10, 1, 0); CHECK(!test_starts);
Setup_DAC_time(); WAKEpowse = 0;
tick(10, 0, 0); CHECK(test_starts == 1 && test_permission);
CHECK(LOAD_CAN_TIMEOUT == 600);
/* Wrong ZPT and masked slots never confirm freshness. */
setup(5); Commands = 0x80;
Load_voltage_can(0x8000U | 8U, 200, 0, 0);
Load_voltage_can(305U, 200, 0, 0); tick(10, 0, 0);
CHECK(!test_starts && (LOAD_STATUS & 2));
Load_voltage_can(0x2000U | 303U, 0, 0, 30);
tick(1, 0, 0); CHECK(test_starts == 1);
/* Freeze current and code at timeout; unrelated exchange cannot unfreeze. */
tick(1000, 1, 30); tick(600, 0, 0);
ramp = ump_dac_ramp; code = modbus[123];
tick(2000, 0, 0); CHECK(ump_dac_ramp == ramp && (unsigned int)modbus[123] == code);
CHECK(test_permission && !test_stop && (LOAD_STATUS & 2));
Load_voltage_can(0x8000U | 8U, 200, 0, 0); tick(10, 0, 0);
CHECK(ump_dac_ramp == ramp);
tick(10, 1, 30); CHECK(ump_dac_ramp > ramp);
/* Exactly 30 V permits growth; 29 V fails strictly above 6 mA. */
setup(6); Commands = 0x80; tick(10, 1, 30);
load_count_ramp = time_dac / 6; tick(5, 1, 30);
CHECK(load_state == LOAD_RUN && modbus[0x1C] >= 60);
tick(5, 1, 29); CHECK(load_state == LOAD_RELOAD && test_stop && !test_permission);
CHECK(!test_start && ump_dac_ramp == 0 && modbus[0x1C] == 40);
CHECK(modbus[123] == DAC_min()); stopped_at = test_last_stop_tick;
tick(999, 1, 0); CHECK(test_starts == 1 && test_stop);
tick(1, 1, 0); CHECK(test_starts == 2 && test_last_start_tick-stopped_at == 1000);
CHECK(!test_stop && test_permission && !test_bad_order);
/* Five total attempts, then a persistent fault; no sixth start. */
while (load_state != LOAD_FAULT) {
load_count_ramp = time_dac / 6; tick(5, 1, 0);
if (load_state == LOAD_RELOAD) tick(1000, 1, 0);
}
CHECK(test_starts == 5 && LOAD_ATTEMPTS == 5 && (LOAD_STATUS & 1));
tick(3000, 1, 200); CHECK(test_starts == 5 && test_stop && !test_permission);
Commands = 0xC0; tick(10, 1, 200); CHECK(load_state == LOAD_FAULT);
Commands = 0; tick(1000, 1, 200); CHECK(!(LOAD_STATUS & 1));
Commands = 0x80; test_starts = 0; tick(10, 1, 200); CHECK(LOAD_ATTEMPTS == 1);
/* Manual reset dominates START; releasing it alone cannot start again. */
Commands = 0xC0; tick(1, 1, 200); CHECK(test_stop && !test_permission);
Commands = 0x80; tick(1500, 1, 200); CHECK(test_starts == 1);
/* Versioned simulation writes are bounded, read-only fields reject writes. */
count = Load_protection_prepare(3, 0x1300, 8, out, &error);
CHECK(count == 8 && !error && out[0] == 0x5550 && out[1] == 1);
Load_protection_prepare(6, 0x1303, 0, out, &error); CHECK(error == 3);
Load_protection_prepare(6, 0x1304, 0, out, &error); CHECK(error == 2);
Load_protection_prepare(6, 0x1302, 4, out, &error); CHECK(error == 3);
setup(5); Commands = 0x80; tick(10, 1, 200);
Load_protection_prepare(6, 0x1303, 20, out, &error); CHECK(!error);
Load_protection_prepare(6, 0x1302, 1, out, &error); CHECK(!error);
tick(30, 1, 200); CHECK(LOAD_STATUS & 2);
ramp = ump_dac_ramp; tick(100, 1, 200); CHECK(ump_dac_ramp == ramp);
Load_protection_prepare(6, 0x1302, 0, out, &error);
tick(10, 1, 200); CHECK(!(LOAD_STATUS & 2) && ump_dac_ramp > ramp);
Load_protection_prepare(6, 0x1302, 2, out, &error);
load_count_ramp = time_dac / 6; tick(5, 1, 200); CHECK(load_state == LOAD_RELOAD);
/* Calibration must not replace the forced 4 mA output during reload. */
Commands |= 0x200; DAC_cal() = 4095; tick(5, 1, 200);
CHECK(modbus[123] == DAC_min() && modbus[0x1C] == 40);
/* A calibration code preceding START must be replaced by 4 mA first. */
setup(5); Commands = 0x200; DAC_cal() = 4095;
tick(10, 1, 200); CHECK(modbus[123] == 4095 && !load_zero_sent);
Commands = 0x280; tick(1, 1, 200); CHECK(!test_starts);
tick(4, 1, 200); CHECK(!test_starts && modbus[123] == DAC_min());
tick(1, 1, 200); CHECK(test_starts == 1);
/* Timeout while STOP is held cannot initiate the pending retry. */
setup(5); Commands = 0x80; tick(10, 1, 0);
load_count_ramp = time_dac / 6; tick(5, 1, 0);
CHECK(load_state == LOAD_RELOAD);
tick(1500, 0, 0); CHECK(test_starts == 1 && test_stop);
tick(1, 1, 0); CHECK(test_starts == 2 && !test_stop);
Load_control_init(); CHECK(!load_simulation);
puts("PASS: production DAC control, timeout, CAN slots, STOP ordering, five attempts, simulation");
return 0;
}

View File

@@ -0,0 +1,16 @@
"""Compile the production DAC.c against GPIO/IRQ mocks; no hardware writes."""
import os
from pathlib import Path
import shutil
import subprocess
import tempfile
root = Path(__file__).resolve().parents[2]
compiler = os.environ.get('CC') or shutil.which('gcc') or 'C:/msys64/mingw64/bin/gcc.exe'
os.environ['PATH'] = str(Path(compiler).resolve().parent) + os.pathsep + os.environ['PATH']
with tempfile.TemporaryDirectory(prefix='load-control-') as tmp:
exe = Path(tmp) / 'test.exe'
subprocess.run([compiler, '-Wall', '-Wextra', '-std=c99',
'-Itests/load_control/stubs', '-ISource/Internal/Include',
'tests/load_control/load_control_test.c', '-o', str(exe)], cwd=root, check=True)
subprocess.run([str(exe)], check=True, timeout=10)

View 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