chore(merge): объедини pysetterminal с main
Сохрани актуальный генератор firmware identity с экспортом всей папки сборки и временем UTC+3 при объединении веток.
This commit is contained in:
275
c/firmware-info/BUILD_IDENTITY.md
Normal file
275
c/firmware-info/BUILD_IDENTITY.md
Normal file
@@ -0,0 +1,275 @@
|
||||
# Версия работающей прошивки и SHA-256 файла: TMS2812, TMS28335, STM32
|
||||
|
||||
## 1. Какие задачи решает механизм
|
||||
|
||||
Механизм продолжает контракт Climate: устройство сообщает SemVer, дату/время
|
||||
сборки и Git Build ID в 12 регистрах. Компьютер после сборки вычисляет SHA-256
|
||||
готового файла и сохраняет паспорт `*.manifest.json` и файл `*.sha256`.
|
||||
|
||||
Это три разных идентификатора:
|
||||
|
||||
| Идентификатор | Что определяет | Где хранится |
|
||||
|---|---|---|
|
||||
| `major.minor.patch` | Назначенный разработчиком номер версии | В образе и паспорте |
|
||||
| Git Build ID | Исходный коммит; `+` отмечает локальные изменения | В образе и паспорте |
|
||||
| SHA-256 | Точные байты конкретного BIN/HEX, включая заголовки | Рядом с образом |
|
||||
|
||||
SHA Git-коммита и SHA-256 файла прошивки не взаимозаменяемы. Один коммит может
|
||||
давать разные бинарные файлы при смене компилятора, опций, времени сборки или
|
||||
зависимостей. Полные коммиты приложения и templates записываются в паспорт.
|
||||
|
||||
Контроллер не вычисляет SHA своей памяти. Ответ версии подтверждает, какие
|
||||
метаданные содержит работающий образ; SHA файла проверяет файл на компьютере.
|
||||
Для проверки фактической записи нужна отдельная штатная проверка программатора
|
||||
или чтение памяти назад. SHA рядом с файлом не является цифровой подписью.
|
||||
|
||||
## 2. Слои и файлы
|
||||
|
||||
```text
|
||||
firmware_identity.json проекта + Git приложения + Git templates
|
||||
|
|
||||
tools/identity.py prepare (до компиляции)
|
||||
|
|
||||
generated/firmware_identity.h + .json
|
||||
|
|
||||
src/firmware_info_static.c (общий C-код)
|
||||
|
|
||||
12 неизменяемых слов -> порт транспорта -> клиент
|
||||
|
||||
линковка -> преобразование BIN -> проверка размера -> identity.py seal
|
||||
|
|
||||
BIN + BIN.manifest.json + BIN.sha256
|
||||
```
|
||||
|
||||
| Файл | Задача и зависимости |
|
||||
|---|---|
|
||||
| `include/firmware_info_static.h` | Контракт компактного порта и упаковка слов, только `stdint.h` |
|
||||
| `src/firmware_info_static.c` | Константная таблица и чтение одного слова; нужен generated header |
|
||||
| `include/firmware_info.h`, `src/firmware_info.c` | Существующий динамический API Climate, валидация и сериализация |
|
||||
| `src/firmware_info_port.c` | Динамический адаптер конфигурации; поддерживает новый UTC stamp |
|
||||
| `ports/tms320f2812`, `ports/tms320f28335`, `ports/stm32` | Примеры конфигурации для нового генератора |
|
||||
| `tools/identity.py` | Git, JSON, SHA-256 файла; Python 3.10+ и Git, без pip-зависимостей |
|
||||
| `tests/test_static.c` | Совпадение компактного и динамического контрактов, чтение RS/CAN, запрет записи |
|
||||
| `tests/test_identity.py` | Генерация, dirty-зависимость, отсутствие Git, порча файла |
|
||||
|
||||
Ядро не знает HAL, CMSIS, XINTF, EEPROM, UART, CAN-регистры и адрес устройства.
|
||||
`firmware_info_static_read(unsigned index)` принимает индекс **0..11**, возвращает
|
||||
`uint16_t`; вне диапазона возвращает 0. Проверять диапазон запроса и запрещать
|
||||
запись обязан транспорт. Нулевую версию контракта клиент показывает как
|
||||
«версия неизвестна», а не подставляет версию из выбранного файла.
|
||||
|
||||
## 3. Конфигурация проекта и генерация
|
||||
|
||||
В репозитории приложения создайте `firmware_identity.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"product": "MyController",
|
||||
"target": "TMS320F2812",
|
||||
"version": [1, 0, 0]
|
||||
}
|
||||
```
|
||||
|
||||
`product` и `target` нужны для паспорта файла. В контракт v1 они не входят:
|
||||
тип подключённого устройства по-прежнему определяет его собственный протокол.
|
||||
Не используйте совпадение версии как разрешение залить образ другого изделия.
|
||||
|
||||
До каждой компиляции выполните (пути приведены для checkout в `lib/templates`):
|
||||
|
||||
```powershell
|
||||
python lib/templates/c/firmware-info/tools/identity.py prepare `
|
||||
--repository . --templates lib/templates `
|
||||
--config firmware_identity.json --output build/generated
|
||||
```
|
||||
|
||||
Добавьте `build/generated` и `lib/templates/c/firmware-info/include` в include
|
||||
paths. Скомпилируйте **один экземпляр**
|
||||
`lib/templates/c/firmware-info/src/firmware_info_static.c`. Если проект включает
|
||||
этот файл через агрегирующий `.c`, отдельно добавлять его в сборку нельзя.
|
||||
|
||||
Генератор намеренно переписывает header при каждом запуске: зависимый объект
|
||||
должен пересобраться. Отсутствие header — ошибка компиляции, а не `LOCALDEV`.
|
||||
Нужна корректная обработка зависимостей сборщиком; после изменения настроек IDE
|
||||
выполните полную пересборку. Не редактируйте исходники во время сборки.
|
||||
|
||||
Для разных конфигураций и параллельных сборок используйте разные `generated`
|
||||
каталоги. Не запускайте две сборки с одним output одновременно.
|
||||
|
||||
Время в новом пути — **UTC**, снято перед компиляцией. Если задан
|
||||
`SOURCE_DATE_EPOCH`, используется этот Unix timestamp; это помогает повторяемым
|
||||
сборкам, но не делает весь toolchain автоматически воспроизводимым.
|
||||
|
||||
### Значения Build ID
|
||||
|
||||
| Значение | Смысл |
|
||||
|---|---|
|
||||
| `a1b2c3d4` | Приложение и templates доступны в Git и оба дерева чистые |
|
||||
| `a1b2c3d+` | Изменено приложение или templates, включая неотслеживаемые файлы |
|
||||
| `NOGIT000` | Не удалось определить хотя бы один из двух Git-репозиториев |
|
||||
| `LOCALDEV` | Только старый динамический путь без generated header |
|
||||
|
||||
Полные commit SHA находятся в `source.commit` и `templates.commit` паспорта.
|
||||
`+` не идентифицирует содержимое локальных правок. Точный файл определяет его
|
||||
SHA-256; для воспроизводимого выпуска нужны сохранённые исходники и настройки.
|
||||
Добавляйте generated/build каталоги в `.gitignore`, иначе они будут честно
|
||||
делать следующую сборку dirty. Намеренно не игнорируйте новые исходные файлы.
|
||||
|
||||
## 4. Формат: одинаковые 12 слов на всех МК
|
||||
|
||||
| Индекс | Значение |
|
||||
|---:|---|
|
||||
| 0 | Версия контракта: 1 |
|
||||
| 1 | Major, 0..255 |
|
||||
| 2 | Minor, 0..255 |
|
||||
| 3 | Patch, 0..999 |
|
||||
| 4 | Год, 2000..2199 |
|
||||
| 5 | Месяц в старшем октете, день в младшем |
|
||||
| 6 | Час в старшем октете, минута в младшем |
|
||||
| 7 | Секунда |
|
||||
| 8 | ASCII Build ID: символы 0 и 1, первый в старшем октете |
|
||||
| 9 | Символы 2 и 3 |
|
||||
| 10 | Символы 4 и 5 |
|
||||
| 11 | Символы 6 и 7 |
|
||||
|
||||
Например, `abcdef0+` даёт `0x6162, 0x6364, 0x6566, 0x302B` в словах 8..11.
|
||||
Modbus передаёт каждое слово старшим октетом вперёд. Старый
|
||||
`firmware_info_to_le_bytes()` передаёт слова little-endian для соответствующих
|
||||
byte-протоколов; эти два представления нельзя путать.
|
||||
|
||||
На TMS320F2812/F28335 `CHAR_BIT == 16`, `sizeof(uint16_t) == 1`, тип `uint8_t`
|
||||
отсутствует. В динамическом API используется `firmware_info_octet`, основанный
|
||||
на `uint_least8_t`: на STM32 это прежние 8 бит, на C28x — контейнер 16 бит.
|
||||
Размеры byte-API означают число **элементов-октетов**, не физический `sizeof`
|
||||
переданного массива. На проводе всё равно 24 восьмибитных октета.
|
||||
|
||||
## 5. Порты и включение в транспорт
|
||||
|
||||
### TMS320F28335 / ПМ35
|
||||
|
||||
В 167 уже подключено через `Source/Internal/shared_ump.c`. Макрос
|
||||
`UMP_FIRMWARE_INFO_READ` связывает общий порт UMP с
|
||||
`firmware_info_static_read`. Диапазон `0x1200..0x120B` обслуживается до
|
||||
обращения к регистратору. Окно параметров `0..127` и лог не расширяются.
|
||||
|
||||
RS: функция 03, начальный адрес `0x1200` (4608), количество 12.
|
||||
Разрешены частичные чтения, не выходящие за конец окна. Функция 06 возвращает
|
||||
исключение 02, не изменяя ни версию, ни настройки регистратора.
|
||||
|
||||
Служебный CAN ПМ35: тот же UMP-запрос функции 03 на `0x1200`, количество 12,
|
||||
ненулевой token. Ответ — шесть фрагментов по два слова с тем же token.
|
||||
Частичные CAN-чтения этого окна отвергаются. Используйте существующие UMP
|
||||
сборщики/парсеры templates, не создавайте второй кодек в GUI.
|
||||
|
||||
### TMS320F2812 / ПМ67
|
||||
|
||||
Добавьте тот же `firmware_info_static.c` и include paths. Компактный порт не
|
||||
требует FPU и не использует регистры F28335. Конфигурация-пример лежит в
|
||||
`ports/tms320f2812/firmware_info_config.template.h`.
|
||||
|
||||
Выберите свободное окно в карте **конкретного ПМ67**. В обработчике чтения
|
||||
отдавайте `firmware_info_static_read(address - base)` после проверки диапазона.
|
||||
Не переносите автоматически CAN ID и UMP ПМ35: у ПМ67 отдельный протокол.
|
||||
Это поставляемый переносимый порт, а не заявление, что конкретная прошивка
|
||||
ПМ67 уже изменена или испытана на устройстве.
|
||||
|
||||
### STM32 / Modbus или SET
|
||||
|
||||
Компактный порт одинаков для F1/F4/G4. Для HAL он не требует ни инициализации,
|
||||
ни callback. Пример адаптации чтения **после проверки диапазона транспортом**:
|
||||
|
||||
```c
|
||||
#include "firmware_info_static.h"
|
||||
|
||||
/* FW_BASE выбирает проект; соответствие адресов проверяет Modbus/SET handler. */
|
||||
uint16_t app_firmware_word(uint16_t address)
|
||||
{
|
||||
unsigned index = (unsigned)(address - FW_BASE);
|
||||
return firmware_info_static_read(index);
|
||||
}
|
||||
```
|
||||
|
||||
Для STM32 UMP-периферии можно использовать уже существующий
|
||||
`set-protocol/ports/stm32f407-periph` и подставить этот reader в `ump_backend`.
|
||||
Для обычного Modbus используйте собственное свободное input/holding read-only
|
||||
окно, сохраняя 12 слов контракта. В Climate адрес был 1960; это **не** адрес
|
||||
167. Если удобнее заполнить RAM-регистры при старте, оставьте динамический API:
|
||||
`firmware_info_port_describe()` и `firmware_info_to_registers()`.
|
||||
|
||||
## 6. SHA-256 и паспорт после сборки
|
||||
|
||||
Сначала завершите линковку и преобразование в файл, который реально отправляется
|
||||
загрузчику. Затем проверьте формат и допустимый размер. Только после успеха:
|
||||
|
||||
```powershell
|
||||
python lib/templates/c/firmware-info/tools/identity.py seal `
|
||||
--image build/controller.bin --metadata build/generated/firmware_identity.json
|
||||
```
|
||||
|
||||
Результат:
|
||||
|
||||
```text
|
||||
controller.bin
|
||||
controller.bin.manifest.json
|
||||
controller.bin.sha256
|
||||
```
|
||||
|
||||
Паспорт содержит product, target, version, build_id, built_at_utc, полные commit
|
||||
SHA и dirty-состояния двух репозиториев, имя файла, размер и SHA-256. Передавайте
|
||||
эти три файла вместе. `seal` не извлекает версию из произвольного бинарника:
|
||||
metadata должен быть из **той же сборки**. Нельзя перепривязать старый BIN к
|
||||
новому pre-build JSON. Проверка версии на работающей плате замыкает этот цикл.
|
||||
|
||||
Проверка точного файла:
|
||||
|
||||
```powershell
|
||||
python lib/templates/c/firmware-info/tools/identity.py verify `
|
||||
--image build/controller.bin --manifest build/controller.bin.manifest.json
|
||||
```
|
||||
|
||||
При изменении даже одного байта команда завершится ненулевым кодом. SHA считается
|
||||
для всего файла: для SCI8 — вместе с boot table, для HEX — для текстового HEX,
|
||||
а не для нормализованного содержимого Flash. Выбирайте BIN для однозначного
|
||||
сравнения с бинарным файлом программатора.
|
||||
|
||||
## 7. Выпуск и проверка на устройстве
|
||||
|
||||
1. Назначьте SemVer в конфигурации приложения.
|
||||
2. Сохраните изменения templates отдельным коммитом и закрепите именно эту
|
||||
зависимость в потребителе. Затем сохраните исходники приложения.
|
||||
3. Убедитесь, что оба дерева чистые; соберите в отдельном output.
|
||||
4. Проверьте ограничения образа, наличие паспорта и результат `verify`.
|
||||
5. Загрузите именно этот файл штатным загрузчиком и перезапустите устройство.
|
||||
6. Прочитайте версию с устройства заново, отключив кэш клиента.
|
||||
7. Сравните все 12 слов с SemVer, UTC stamp и Build ID паспорта.
|
||||
8. Сохраните BIN, паспорт, настройки компилятора и результаты проверки вместе.
|
||||
|
||||
Изменение SemVer не должно менять `contract_version`: последняя меняется только
|
||||
при несовместимом изменении формата. При несовпадении даты/Build ID сначала
|
||||
проверьте выбранный BIN, конфигурацию IDE, generated include path и полную
|
||||
пересборку, затем адрес и кэш клиента.
|
||||
|
||||
## 8. Автоматические проверки
|
||||
|
||||
```powershell
|
||||
cmake -S c/firmware-info -B build/firmware-info
|
||||
cmake --build build/firmware-info
|
||||
ctest --test-dir build/firmware-info --output-on-failure
|
||||
python -m unittest discover -s c/firmware-info/tests -p test_identity.py
|
||||
```
|
||||
|
||||
Дополнительно компилируйте core/static через TI C2000 (`-v28 -ml`) и ARM
|
||||
toolchain. Host-тест не заменяет проверку C28x ABI и фактического обмена с платой.
|
||||
Для 167 есть отдельный `tests/firmware_identity/port_test.c`: он проверяет
|
||||
реальную привязку reader, RTU CRC, CAN prepare и сохранение окна регистратора.
|
||||
|
||||
## 9. Совместимость с существующими проектами
|
||||
|
||||
Старый `make_build_id.ps1`, конфигурации STM32F1/F4/G4 и динамический API остаются.
|
||||
Новый генератор не подключается к другим проектам сам: добавьте pre/post-build
|
||||
и generated include path по этой инструкции. На STM32 размеры публичных полей
|
||||
и сигнатура byte-буфера остаются прежними благодаря `uint_least8_t`.
|
||||
|
||||
Изменения сделаны в checkout templates, который уже использует 167:
|
||||
`newProject/SETGUI/third_party/templates`. Другие checkout/submodule не следует
|
||||
обновлять копированием отдельных `.c`: перенесите проверенный коммит templates
|
||||
и обновите ссылку зависимости. ПМ67 и ПМ35 сохраняют разные транспортные порты.
|
||||
@@ -10,3 +10,8 @@ add_executable(test_firmware_info tests/test_firmware_info.c)
|
||||
target_link_libraries(test_firmware_info PRIVATE firmware_info)
|
||||
enable_testing()
|
||||
add_test(NAME firmware_info COMMAND test_firmware_info)
|
||||
add_executable(test_firmware_info_static tests/test_static.c src/firmware_info_static.c
|
||||
../set-protocol/src/ump_protocol.c)
|
||||
target_include_directories(test_firmware_info_static PRIVATE tests/fixtures ../set-protocol/include)
|
||||
target_link_libraries(test_firmware_info_static PRIVATE firmware_info)
|
||||
add_test(NAME firmware_info_static COMMAND test_firmware_info_static)
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# Портирование firmware-info
|
||||
|
||||
Для компактного пути без парсера даты, TMS320F2812/F28335 и STM32, а также
|
||||
SHA-256/паспортов сборки используйте [BUILD_IDENTITY.md](BUILD_IDENTITY.md).
|
||||
Ниже сохранена инструкция для совместимого динамического API.
|
||||
|
||||
## Назначение и границы
|
||||
|
||||
Это C-библиотека описания работающего образа: SemVer, дата/время компиляции,
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# Firmware Info
|
||||
|
||||
Компактный вариант для TMS320F2812, TMS320F28335 и STM32, общий pre/post-build,
|
||||
SHA-256 файла и паспорт сборки: [подробная инструкция](BUILD_IDENTITY.md).
|
||||
Он сохраняет 12-словный контракт Climate и не требует парсера даты на MCU.
|
||||
|
||||
Переносимая библиотека версии реально запущенной прошивки. Повторяет контракт
|
||||
`FirmwareInfo` из `john103C6T6NewVer`, но оформлена как template: ядро не знает
|
||||
ни о HAL, ни о конкретном протоколе, ни о модели МК.
|
||||
|
||||
@@ -10,6 +10,10 @@
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/* Least-width octet storage preserves STM ABI, and compiles on C28x where
|
||||
* CHAR_BIT=16 and uint8_t does not exist. Values sent on wire are masked to 8 bits. */
|
||||
typedef uint_least8_t firmware_info_octet;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
@@ -24,11 +28,11 @@ typedef struct {
|
||||
uint16_t minor;
|
||||
uint16_t patch;
|
||||
uint16_t year;
|
||||
uint8_t month;
|
||||
uint8_t day;
|
||||
uint8_t hour;
|
||||
uint8_t minute;
|
||||
uint8_t second;
|
||||
firmware_info_octet month;
|
||||
firmware_info_octet day;
|
||||
firmware_info_octet hour;
|
||||
firmware_info_octet minute;
|
||||
firmware_info_octet second;
|
||||
char build_id[FIRMWARE_INFO_BUILD_ID_SIZE + 1U];
|
||||
} firmware_info_t;
|
||||
|
||||
@@ -56,7 +60,7 @@ firmware_info_status_t firmware_info_set_build_id(
|
||||
firmware_info_status_t firmware_info_to_registers(
|
||||
const firmware_info_t *info, uint16_t *registers, size_t count);
|
||||
firmware_info_status_t firmware_info_to_le_bytes(
|
||||
const firmware_info_t *info, uint8_t *payload, size_t size);
|
||||
const firmware_info_t *info, firmware_info_octet *payload, size_t size);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
22
c/firmware-info/include/firmware_info_static.h
Normal file
22
c/firmware-info/include/firmware_info_static.h
Normal file
@@ -0,0 +1,22 @@
|
||||
/* Small, immutable firmware identity for targets with little code space.
|
||||
* The build tool supplies scalar constants; the C core owns register packing.
|
||||
* No startup parser, RAM copy, heap, HAL or runtime SHA implementation is needed.
|
||||
* Include firmware_identity.h (generated BEFORE compilation) before this file.
|
||||
*/
|
||||
#ifndef FIRMWARE_INFO_STATIC_H
|
||||
#define FIRMWARE_INFO_STATIC_H
|
||||
#include <stdint.h>
|
||||
|
||||
#define FIRMWARE_INFO_STATIC_COUNT 12U
|
||||
/* Cast BEFORE shifting: an int is only 16 bits on both C28x targets. */
|
||||
#define FI_PAIR(high, low) ((uint16_t)(((uint16_t)(high) << 8U) | (uint16_t)(low)))
|
||||
#define FIRMWARE_INFO_STATIC_INITIALIZER { \
|
||||
1U, FIRMWARE_VERSION_MAJOR, FIRMWARE_VERSION_MINOR, FIRMWARE_VERSION_PATCH, \
|
||||
FI_YEAR, FI_PAIR(FI_MONTH, FI_DAY), FI_PAIR(FI_HOUR, FI_MINUTE), FI_SECOND, \
|
||||
FI_PAIR(FI_ID_0, FI_ID_1), FI_PAIR(FI_ID_2, FI_ID_3), \
|
||||
FI_PAIR(FI_ID_4, FI_ID_5), FI_PAIR(FI_ID_6, FI_ID_7) }
|
||||
|
||||
/* Return zero outside the contract. The transport must reject invalid ranges
|
||||
* before calling this function; zero is NOT an invented firmware version. */
|
||||
uint16_t firmware_info_static_read(unsigned index);
|
||||
#endif
|
||||
@@ -0,0 +1,8 @@
|
||||
#ifndef FIRMWARE_INFO_CONFIG_H
|
||||
#define FIRMWARE_INFO_CONFIG_H
|
||||
/* Family-independent STM32 static port: no HAL/CMSIS dependency.
|
||||
* F1/F4/G4 applications may use this instead of the runtime date parser.
|
||||
* Generate the header before compilation, then compile firmware_info_static.c.
|
||||
* Modbus sends each returned word high octet first; SET uses its own encoder. */
|
||||
#include "firmware_identity.h"
|
||||
#endif
|
||||
@@ -0,0 +1,7 @@
|
||||
#ifndef FIRMWARE_INFO_CONFIG_H
|
||||
#define FIRMWARE_INFO_CONFIG_H
|
||||
/* Same C28x word representation as F28335, but transport remains PM67-specific.
|
||||
* Do not route PM67 commands through the PM35 logger/CAN adapter.
|
||||
* Compile firmware_info_static.c and expose its 12 words in a read-only window. */
|
||||
#include "firmware_identity.h"
|
||||
#endif
|
||||
@@ -0,0 +1,7 @@
|
||||
#ifndef FIRMWARE_INFO_CONFIG_H
|
||||
#define FIRMWARE_INFO_CONFIG_H
|
||||
/* Generate before EVERY build. C28x has 16-bit char and no uint8_t.
|
||||
* For the compact port compile firmware_info_static.c, not the date parser.
|
||||
* The same generated header can also configure firmware_info_port.c. */
|
||||
#include "firmware_identity.h"
|
||||
#endif
|
||||
@@ -13,20 +13,20 @@ static const char months[12][4] = {
|
||||
|
||||
static int is_digit(char value) { return (value >= '0') && (value <= '9'); }
|
||||
|
||||
static int two_digits(const char *text, uint8_t *result)
|
||||
static int two_digits(const char *text, firmware_info_octet *result)
|
||||
{
|
||||
char high = text[0];
|
||||
char low = text[1];
|
||||
if (high == ' ') high = '0';
|
||||
if (!is_digit(high) || !is_digit(low)) return 0;
|
||||
*result = (uint8_t)(((uint8_t)(high - '0') * 10U) + (uint8_t)(low - '0'));
|
||||
*result = (firmware_info_octet)(((firmware_info_octet)(high - '0') * 10U) + (firmware_info_octet)(low - '0'));
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int four_digits(const char *text, uint16_t *result)
|
||||
{
|
||||
uint16_t value = 0U;
|
||||
uint8_t i;
|
||||
firmware_info_octet i;
|
||||
for (i = 0U; i < 4U; ++i) {
|
||||
if (!is_digit(text[i])) return 0;
|
||||
value = (uint16_t)(value * 10U + (uint16_t)(text[i] - '0'));
|
||||
@@ -37,7 +37,7 @@ static int four_digits(const char *text, uint16_t *result)
|
||||
|
||||
firmware_info_status_t firmware_info_validate(const firmware_info_t *info)
|
||||
{
|
||||
uint8_t i;
|
||||
firmware_info_octet i;
|
||||
if (info == NULL) return FIRMWARE_INFO_INVALID_ARGUMENT;
|
||||
if ((info->major > 255U) || (info->minor > 255U) || (info->patch > 999U)
|
||||
|| (info->year < 2000U) || (info->year > 2199U)
|
||||
@@ -56,7 +56,7 @@ firmware_info_status_t firmware_info_validate(const firmware_info_t *info)
|
||||
firmware_info_status_t firmware_info_set_build_id(firmware_info_t *info,
|
||||
const char *build_id)
|
||||
{
|
||||
uint8_t i;
|
||||
firmware_info_octet i;
|
||||
if ((info == NULL) || (build_id == NULL)) return FIRMWARE_INFO_INVALID_ARGUMENT;
|
||||
for (i = 0U; i < FIRMWARE_INFO_BUILD_ID_SIZE && build_id[i] != '\0'; ++i) {
|
||||
if ((build_id[i] < 0x20) || (build_id[i] > 0x7e))
|
||||
@@ -71,8 +71,8 @@ firmware_info_status_t firmware_info_set_build_id(firmware_info_t *info,
|
||||
firmware_info_status_t firmware_info_parse_build_stamp(
|
||||
const char *date_text, const char *time_text, firmware_info_t *info)
|
||||
{
|
||||
uint8_t month;
|
||||
uint8_t found = 0U;
|
||||
firmware_info_octet month;
|
||||
firmware_info_octet found = 0U;
|
||||
if ((date_text == NULL) || (time_text == NULL) || (info == NULL))
|
||||
return FIRMWARE_INFO_INVALID_ARGUMENT;
|
||||
for (month = 0U; month < 12U; ++month) {
|
||||
@@ -84,14 +84,14 @@ firmware_info_status_t firmware_info_parse_build_stamp(
|
||||
|| !two_digits(&time_text[0], &info->hour)
|
||||
|| !two_digits(&time_text[3], &info->minute)
|
||||
|| !two_digits(&time_text[6], &info->second)) return FIRMWARE_INFO_OUT_OF_RANGE;
|
||||
info->month = (uint8_t)(month + 1U);
|
||||
info->month = (firmware_info_octet)(month + 1U);
|
||||
return FIRMWARE_INFO_OK;
|
||||
}
|
||||
|
||||
firmware_info_status_t firmware_info_to_registers(
|
||||
const firmware_info_t *info, uint16_t *registers, size_t count)
|
||||
{
|
||||
uint8_t i;
|
||||
firmware_info_octet i;
|
||||
firmware_info_status_t status;
|
||||
if ((registers == NULL) || (count < FIRMWARE_INFO_REGISTER_COUNT))
|
||||
return FIRMWARE_INFO_INVALID_ARGUMENT;
|
||||
@@ -104,24 +104,24 @@ firmware_info_status_t firmware_info_to_registers(
|
||||
registers[6] = (uint16_t)(((uint16_t)info->hour << 8U) | info->minute);
|
||||
registers[7] = info->second;
|
||||
for (i = 0U; i < 4U; ++i)
|
||||
registers[8U + i] = (uint16_t)(((uint16_t)(uint8_t)info->build_id[i * 2U] << 8U)
|
||||
| (uint8_t)info->build_id[i * 2U + 1U]);
|
||||
registers[8U + i] = (uint16_t)(((uint16_t)(firmware_info_octet)info->build_id[i * 2U] << 8U)
|
||||
| (firmware_info_octet)info->build_id[i * 2U + 1U]);
|
||||
return FIRMWARE_INFO_OK;
|
||||
}
|
||||
|
||||
firmware_info_status_t firmware_info_to_le_bytes(
|
||||
const firmware_info_t *info, uint8_t *payload, size_t size)
|
||||
const firmware_info_t *info, firmware_info_octet *payload, size_t size)
|
||||
{
|
||||
uint16_t words[FIRMWARE_INFO_REGISTER_COUNT];
|
||||
uint8_t i;
|
||||
firmware_info_octet i;
|
||||
firmware_info_status_t status;
|
||||
if ((payload == NULL) || (size < FIRMWARE_INFO_PAYLOAD_SIZE))
|
||||
return FIRMWARE_INFO_INVALID_ARGUMENT;
|
||||
status = firmware_info_to_registers(info, words, FIRMWARE_INFO_REGISTER_COUNT);
|
||||
if (status != FIRMWARE_INFO_OK) return status;
|
||||
for (i = 0U; i < FIRMWARE_INFO_REGISTER_COUNT; ++i) {
|
||||
payload[i * 2U] = (uint8_t)words[i];
|
||||
payload[i * 2U + 1U] = (uint8_t)(words[i] >> 8U);
|
||||
payload[i * 2U] = (firmware_info_octet)(words[i] & 0xffU);
|
||||
payload[i * 2U + 1U] = (firmware_info_octet)(words[i] >> 8U);
|
||||
}
|
||||
return FIRMWARE_INFO_OK;
|
||||
}
|
||||
|
||||
@@ -15,5 +15,13 @@ firmware_info_status_t firmware_info_port_describe(firmware_info_t *info)
|
||||
info->patch = (uint16_t)FIRMWARE_VERSION_PATCH;
|
||||
status = firmware_info_set_build_id(info, FIRMWARE_BUILD_ID);
|
||||
if (status != FIRMWARE_INFO_OK) return status;
|
||||
#ifdef FI_YEAR
|
||||
/* New build pipeline: use the same UTC stamp as the static port/manifest.
|
||||
* Existing consumers of make_build_id.ps1 keep their compiler-local stamp. */
|
||||
info->year = FI_YEAR; info->month = FI_MONTH; info->day = FI_DAY;
|
||||
info->hour = FI_HOUR; info->minute = FI_MINUTE; info->second = FI_SECOND;
|
||||
return firmware_info_validate(info);
|
||||
#else
|
||||
return firmware_info_parse_build_stamp(__DATE__, __TIME__, info);
|
||||
#endif
|
||||
}
|
||||
|
||||
15
c/firmware-info/src/firmware_info_static.c
Normal file
15
c/firmware-info/src/firmware_info_static.c
Normal file
@@ -0,0 +1,15 @@
|
||||
/* A required generated include deliberately makes a missing pre-build fail.
|
||||
* Silently using LOCALDEV here could disguise an obsolete linked object. */
|
||||
#include "firmware_identity.h"
|
||||
#include "firmware_info_static.h"
|
||||
|
||||
/* On STM these 12 words occupy 24 bytes; on C28x sizeof reports 12 addressable
|
||||
* 16-bit units, which are still 24 wire octets. Never use sizeof as a wire size.
|
||||
* const keeps the identity inside the firmware, away from saved parameters. */
|
||||
static const uint16_t firmware_identity_words[FIRMWARE_INFO_STATIC_COUNT] =
|
||||
FIRMWARE_INFO_STATIC_INITIALIZER;
|
||||
|
||||
uint16_t firmware_info_static_read(unsigned index)
|
||||
{
|
||||
return index < FIRMWARE_INFO_STATIC_COUNT ? firmware_identity_words[index] : 0U;
|
||||
}
|
||||
19
c/firmware-info/tests/fixtures/firmware_identity.h
vendored
Normal file
19
c/firmware-info/tests/fixtures/firmware_identity.h
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
/* Deliberately fixed vector, not production identity. Used only by host tests. */
|
||||
#define FIRMWARE_VERSION_MAJOR 1U
|
||||
#define FIRMWARE_VERSION_MINOR 2U
|
||||
#define FIRMWARE_VERSION_PATCH 3U
|
||||
#define FI_YEAR 2026U
|
||||
#define FI_MONTH 10U
|
||||
#define FI_DAY 5U
|
||||
#define FI_HOUR 12U
|
||||
#define FI_MINUTE 34U
|
||||
#define FI_SECOND 56U
|
||||
#define FIRMWARE_BUILD_ID "abcdef0+"
|
||||
#define FI_ID_0 'a'
|
||||
#define FI_ID_1 'b'
|
||||
#define FI_ID_2 'c'
|
||||
#define FI_ID_3 'd'
|
||||
#define FI_ID_4 'e'
|
||||
#define FI_ID_5 'f'
|
||||
#define FI_ID_6 '0'
|
||||
#define FI_ID_7 '+'
|
||||
74
c/firmware-info/tests/test_identity.py
Normal file
74
c/firmware-info/tests/test_identity.py
Normal file
@@ -0,0 +1,74 @@
|
||||
"""Host lifecycle tests: dirty dependencies, reproducible stamps, file integrity.
|
||||
|
||||
Run: python -m unittest discover -s c/firmware-info/tests -p test_identity.py
|
||||
"""
|
||||
import importlib.util
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import tempfile
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
|
||||
TOOL = Path(__file__).resolve().parents[1] / "tools/identity.py"
|
||||
spec = importlib.util.spec_from_file_location("identity", TOOL)
|
||||
identity = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(identity)
|
||||
|
||||
|
||||
class IdentityTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.temp = tempfile.TemporaryDirectory()
|
||||
self.addCleanup(self.temp.cleanup)
|
||||
self.root = Path(self.temp.name)
|
||||
self.config = self.root / "config.json"
|
||||
self.config.write_text(json.dumps({"product":"test", "target":"host", "version":[1,2,3]}))
|
||||
|
||||
def prepare(self, states):
|
||||
with patch.object(identity, "git_state", side_effect=states), patch.dict(os.environ, {"SOURCE_DATE_EPOCH":"1791194400"}):
|
||||
return identity.prepare(self.root, self.root, self.config, self.root)
|
||||
|
||||
def test_clean_and_dirty_dependency(self):
|
||||
clean = {"commit":"abcdef0123456789", "dirty":False}
|
||||
meta = self.prepare([clean,clean])
|
||||
self.assertEqual(meta["build_id"], "abcdef01")
|
||||
header = (self.root / "firmware_identity.h").read_text()
|
||||
self.assertIn("#define FIRMWARE_VERSION_PATCH 3U",header)
|
||||
dirty = dict(clean, dirty=True)
|
||||
self.assertEqual(self.prepare([clean,dirty])["build_id"], "abcdef0+")
|
||||
self.assertEqual(self.prepare([dirty,clean])["build_id"], "abcdef0+")
|
||||
|
||||
def test_missing_git_is_explicit(self):
|
||||
unknown = {"commit":None,"dirty":None}
|
||||
self.assertEqual(self.prepare([unknown,unknown])["build_id"], "NOGIT000")
|
||||
|
||||
def test_invalid_version(self):
|
||||
self.config.write_text(json.dumps({"product":"x","target":"x","version":[1,2,1000]}))
|
||||
with self.assertRaises(ValueError):
|
||||
identity.prepare(self.root,self.root,self.config,self.root)
|
||||
|
||||
def test_untracked_source_is_dirty(self):
|
||||
def git(*args):
|
||||
subprocess.run(["git","-C",str(self.root),*args],check=True,capture_output=True)
|
||||
git("init")
|
||||
git("-c","user.name=Test","-c","user.email=test@example.invalid","commit","--allow-empty","-m","test")
|
||||
# config.json is untracked and must prevent a clean-looking build ID.
|
||||
self.assertTrue(identity.git_state(self.root)["dirty"])
|
||||
|
||||
def test_cli_seal_verify_and_corruption(self):
|
||||
clean = {"commit":"abcdef0123456789", "dirty":False}
|
||||
self.prepare([clean,clean])
|
||||
image = self.root / "firmware.bin"
|
||||
image.write_bytes(b"abc")
|
||||
import sys
|
||||
subprocess.run([sys.executable,str(TOOL),"seal","--image",str(image),"--metadata",str(self.root/"firmware_identity.json")],check=True,capture_output=True)
|
||||
manifest = image.with_suffix(".bin.manifest.json")
|
||||
meta = identity.verify(image,manifest)
|
||||
self.assertEqual(meta["sha256"],"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad")
|
||||
image.write_bytes(b"abd")
|
||||
with self.assertRaises(ValueError): identity.verify(image,manifest)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
53
c/firmware-info/tests/test_static.c
Normal file
53
c/firmware-info/tests/test_static.c
Normal file
@@ -0,0 +1,53 @@
|
||||
/* One set of vectors checks static metadata against Climate's runtime encoder.
|
||||
* This catches word order and ASCII order changes on the host, before MCU use. */
|
||||
#include "firmware_identity.h"
|
||||
#include "firmware_info.h"
|
||||
#include "firmware_info_static.h"
|
||||
#include "ump_protocol.h"
|
||||
#include <assert.h>
|
||||
|
||||
static unsigned writes;
|
||||
static uint16_t read_identity(void *context, uint16_t address)
|
||||
{
|
||||
(void)context;
|
||||
return firmware_info_static_read(address - UMP_FIRMWARE_INFO);
|
||||
}
|
||||
static int write_identity(void *context, uint16_t address, uint16_t value)
|
||||
{
|
||||
(void)context; (void)address; (void)value;
|
||||
writes++;
|
||||
return 1;
|
||||
}
|
||||
int main(void)
|
||||
{
|
||||
firmware_info_t info;
|
||||
uint16_t expected[12], reply[UMP_MAX_WORDS];
|
||||
ump_octet frame[UMP_RTU_CAPACITY];
|
||||
ump_backend backend = {0, read_identity, write_identity};
|
||||
unsigned i, error;
|
||||
info.major = FIRMWARE_VERSION_MAJOR; info.minor = FIRMWARE_VERSION_MINOR;
|
||||
info.patch = FIRMWARE_VERSION_PATCH; info.year = FI_YEAR;
|
||||
info.month = FI_MONTH; info.day = FI_DAY; info.hour = FI_HOUR;
|
||||
info.minute = FI_MINUTE; info.second = FI_SECOND;
|
||||
assert(firmware_info_set_build_id(&info, FIRMWARE_BUILD_ID) == FIRMWARE_INFO_OK);
|
||||
assert(firmware_info_to_registers(&info, expected, 12) == FIRMWARE_INFO_OK);
|
||||
for (i=0; i<12; ++i) assert(firmware_info_static_read(i) == expected[i]);
|
||||
assert(firmware_info_static_read(12) == 0);
|
||||
assert(ump_prepare(&backend, 3, UMP_FIRMWARE_INFO, 12, 1, reply, &error) == 12 && !error);
|
||||
for (i=0; i<12; ++i) assert(reply[i] == expected[i]);
|
||||
for (i=0; i<6; ++i) {
|
||||
assert(ump_can_fragment(reply,12,i,42,0,frame) == 8);
|
||||
assert(frame[0] == expected[2*i] >> 8 && frame[1] == (expected[2*i] & 255));
|
||||
assert(frame[6] == 42 && frame[7] == 0);
|
||||
}
|
||||
assert(ump_rtu_response(&backend, 1, 3, UMP_FIRMWARE_INFO, 12, frame, sizeof(frame)/sizeof(frame[0])) == 29);
|
||||
assert(frame[2] == 24 && ump_crc(frame,29) == 0);
|
||||
assert(ump_validate(3, UMP_FIRMWARE_INFO+11, 1, 0) == 0);
|
||||
assert(ump_validate(3, UMP_FIRMWARE_INFO+11, 2, 0) == 2);
|
||||
assert(ump_validate(3, UMP_FIRMWARE_INFO, 0, 0) == 2);
|
||||
assert(ump_validate(3, UMP_FIRMWARE_INFO, 0xffffU, 0) == 2);
|
||||
assert(ump_validate(3, UMP_FIRMWARE_INFO+1, 11, 1) == 2);
|
||||
ump_prepare(&backend,6,UMP_FIRMWARE_INFO,1,0,reply,&error);
|
||||
assert(error == 2 && writes == 0);
|
||||
return 0;
|
||||
}
|
||||
@@ -149,6 +149,14 @@ if(SETP_BUILD_TESTS)
|
||||
add_executable(test_periph28335 tests/test_periph28335.c)
|
||||
target_link_libraries(test_periph28335 PRIVATE setprotocol_static)
|
||||
add_test(NAME shared_periph28335 COMMAND test_periph28335)
|
||||
add_executable(test_periph28335_eeprom tests/test_periph28335_eeprom.c)
|
||||
target_link_libraries(test_periph28335_eeprom PRIVATE setprotocol_static)
|
||||
if(MSVC)
|
||||
target_compile_options(test_periph28335_eeprom PRIVATE /UNDEBUG)
|
||||
else()
|
||||
target_compile_options(test_periph28335_eeprom PRIVATE -UNDEBUG)
|
||||
endif()
|
||||
add_test(NAME periph28335_eeprom COMMAND test_periph28335_eeprom)
|
||||
add_executable(test_tms2812 tests/test_tms2812.c)
|
||||
target_link_libraries(test_tms2812 PRIVATE setprotocol_static)
|
||||
add_test(NAME shared_tms2812 COMMAND test_tms2812)
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# SETProtocol
|
||||
|
||||
`periph28335_eeprom_*` — read-only reader физической EEPROM Бальзам-167
|
||||
(AT25256B, 32 КиБ), со стабильным C ABI и портом
|
||||
`python/protocan/periph28335_eeprom.py`. Разметка платы задана в
|
||||
`include/balsam167_eeprom_layout.h`; PEEK, CRC и порядок байтов реализованы
|
||||
в `src/periph28335.c`. Проверка полного диапазона — `test_periph28335_eeprom`.
|
||||
|
||||
Универсальные сервисы эмуляторов: [адресное пространство, API и порт новой платы](docs/EMULATOR_SERVICES.md).
|
||||
`set_regmap` объединяет УМП, 2812, IGBT и DAC за общим API для RTU, CAN и других оболочек.
|
||||
|
||||
|
||||
44
c/set-protocol/include/balsam167_eeprom_block.h
Normal file
44
c/set-protocol/include/balsam167_eeprom_block.h
Normal file
@@ -0,0 +1,44 @@
|
||||
/* Private PM35 FC03 EEPROM window; not standard Modbus register quantities.
|
||||
* AT25256B keeps its existing 0x8000..0xBFFF / count 1..120 requests.
|
||||
* M95M02 uses four 64-KiB windows: count bit15 marks the profile, bits9:8
|
||||
* select bank 0..3, bits7:0 carry word count. Address 0x8000..0xFFFF is
|
||||
* the word offset within a bank. Every request is self-contained; no global
|
||||
* bank register can be changed by another client. Old firmware rejects the
|
||||
* marked quantity. Neither profile silently reads the other memory type.
|
||||
*/
|
||||
#ifndef BALSAM167_EEPROM_BLOCK_H
|
||||
#define BALSAM167_EEPROM_BLOCK_H
|
||||
#include "balsam167_eeprom_layout.h"
|
||||
#define BALSAM167_EEPROM_RTU_BASE 0x8000U
|
||||
#define BALSAM167_EEPROM_BLOCK_WORDS 120U
|
||||
#define BALSAM167_EEPROM_M95_MARKER 0x8000U
|
||||
#define BALSAM167_EEPROM_WRITE_M95 0x44U
|
||||
static unsigned long balsam167_eeprom_capacity(unsigned profile)
|
||||
{
|
||||
return profile == 0 ? 32768UL : (profile == 1 ? 262144UL : 0UL);
|
||||
}
|
||||
static unsigned long balsam167_eeprom_block_word(unsigned address, unsigned count)
|
||||
{
|
||||
return ((unsigned long)(count & 0x0300U) << 7) + (address - 0x8000U);
|
||||
}
|
||||
static unsigned balsam167_eeprom_block_valid_profile(unsigned fn, unsigned address,
|
||||
unsigned count, unsigned profile)
|
||||
{
|
||||
unsigned n = count & 255U;
|
||||
unsigned long word;
|
||||
if (fn != 3 || address < 0x8000U || !n || n > 120U) return 0;
|
||||
if (profile == 0) return count == n && address < 0xC000U && n <= 0xC000U-address;
|
||||
if (profile != 1 || (count & 0xFC00U) != 0x8000U) return 0;
|
||||
word = balsam167_eeprom_block_word(address, count);
|
||||
return n <= 0x10000UL-address && word+n <= 131072UL;
|
||||
}
|
||||
static unsigned balsam167_eeprom_block_valid(unsigned fn, unsigned address, unsigned count)
|
||||
{
|
||||
#if EEPROM_M95M02
|
||||
return balsam167_eeprom_block_valid_profile(fn, address, count, 1);
|
||||
#else
|
||||
return fn == 3 && address >= 0x8000U && address < 0xC000U &&
|
||||
count && count <= 120U && count <= 0xC000U-address;
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
29
c/set-protocol/include/balsam167_eeprom_layout.h
Normal file
29
c/set-protocol/include/balsam167_eeprom_layout.h
Normal file
@@ -0,0 +1,29 @@
|
||||
/* Board-specific layout shared by firmware, linker and host tools.
|
||||
* Fitted part: AT25256B, top marking 5EBL = 256 Kbit = 32768 OCTETS.
|
||||
* ATMLH516 is a manufacturer/date code, not a 512-Kbit density code.
|
||||
* Numeric defines intentionally have no C suffix: TI linker also includes this.
|
||||
*/
|
||||
#ifndef BALSAM167_EEPROM_LAYOUT_H
|
||||
#define BALSAM167_EEPROM_LAYOUT_H
|
||||
/* EEPROM_M95M02 is selected by the board configuration, never autodetected
|
||||
* by writing test patterns. Keep numeric constants usable by the TI linker. */
|
||||
#define BALSAM167_AT25256_BYTES 0x8000
|
||||
#define BALSAM167_M95M02_BYTES 0x40000
|
||||
#ifndef EEPROM_M95M02
|
||||
#define EEPROM_M95M02 0
|
||||
#endif
|
||||
#if EEPROM_M95M02
|
||||
#define BALSAM167_EEPROM_BYTES BALSAM167_M95M02_BYTES
|
||||
#define BALSAM167_EEPROM_PAGE_BYTES 256
|
||||
#else
|
||||
#define BALSAM167_EEPROM_BYTES BALSAM167_AT25256_BYTES
|
||||
#define BALSAM167_EEPROM_PAGE_BYTES 64
|
||||
#endif
|
||||
#define BALSAM167_EEPROM_PARAM_WORDS 129
|
||||
#define BALSAM167_EEPROM_WORDS (BALSAM167_EEPROM_BYTES / 2)
|
||||
/* Preserve the historical end-exclusive parameter boundary. The final word
|
||||
* remains unused; moving it would break existing saved configurations. */
|
||||
#define BALSAM167_EEPROM_PARAMS_END_WORD (BALSAM167_EEPROM_WORDS - 1)
|
||||
#define BALSAM167_EEPROM_PARAMS_START_WORD (BALSAM167_EEPROM_PARAMS_END_WORD - BALSAM167_EEPROM_PARAM_WORDS)
|
||||
#define BALSAM167_EEPROM_FIRMWARE_BYTES (BALSAM167_EEPROM_PARAMS_START_WORD * 2)
|
||||
#endif
|
||||
@@ -82,6 +82,25 @@ PCAN_ABI_API size_t pcan_abi_balsam_register_name(
|
||||
/* PM35/TMS320F28335 direct RS232/485 register terminal. */
|
||||
PCAN_ABI_API uint16_t pcan_abi_periph28335_crc16(
|
||||
const uint8_t *data, size_t size);
|
||||
/* Caller-owned EEPROM reader state: uint32_t[4], 16 bytes, see periph28335.h.
|
||||
* Exported independently of PM67: the EEPROM routing is specific to 167. */
|
||||
PCAN_ABI_API int pcan_abi_periph28335_eeprom_begin(void *state,
|
||||
uint32_t controller, uint32_t start, uint32_t length);
|
||||
PCAN_ABI_API size_t pcan_abi_periph28335_eeprom_request(const void *state,
|
||||
uint8_t *output, size_t capacity);
|
||||
PCAN_ABI_API int pcan_abi_periph28335_eeprom_accept(void *state,
|
||||
const uint8_t *reply, size_t size, uint8_t *output, size_t capacity);
|
||||
PCAN_ABI_API uint32_t pcan_abi_periph28335_eeprom_capacity(void);
|
||||
PCAN_ABI_API uint32_t pcan_abi_periph28335_eeprom_capacity_profile(unsigned profile);
|
||||
PCAN_ABI_API int pcan_abi_periph28335_eeprom_begin_profile(void *r, uint32_t c, uint32_t a, uint32_t n, unsigned profile);
|
||||
PCAN_ABI_API size_t pcan_abi_periph28335_eeprom_block_expected_profile(const void *r, unsigned profile);
|
||||
PCAN_ABI_API size_t pcan_abi_periph28335_eeprom_block_request_profile(const void *r, uint8_t *o, size_t n, unsigned profile);
|
||||
PCAN_ABI_API int pcan_abi_periph28335_eeprom_block_accept_profile(void *r, const uint8_t *p, size_t n, uint8_t *o, size_t cap, unsigned profile);
|
||||
PCAN_ABI_API size_t pcan_abi_periph28335_eeprom_write_profile(uint32_t controller, uint32_t destination, uint32_t source, uint32_t length, unsigned profile, uint8_t *out, size_t cap);
|
||||
PCAN_ABI_API size_t pcan_abi_periph28335_eeprom_block_expected(const void *state);
|
||||
PCAN_ABI_API size_t pcan_abi_periph28335_eeprom_block_request(const void *state, uint8_t *out, size_t cap);
|
||||
PCAN_ABI_API int pcan_abi_periph28335_eeprom_block_accept(void *state, const uint8_t *reply, size_t size, uint8_t *out, size_t cap);
|
||||
|
||||
PCAN_ABI_API size_t pcan_abi_periph28335_append_crc(
|
||||
const uint8_t *payload, size_t payload_size,
|
||||
uint8_t *output, size_t output_size);
|
||||
@@ -110,6 +129,9 @@ PCAN_ABI_API const char *pcan_abi_periph28335_command_name(
|
||||
/* PM67/TMS320F2812 legacy memory upload. */
|
||||
PCAN_ABI_API size_t pcan_abi_tms2812_build_fixed(uint8_t controller, uint8_t command,
|
||||
const uint8_t *payload, size_t payload_size, uint8_t *output, size_t output_size);
|
||||
PCAN_ABI_API int pcan_abi_tms2812_bus_step(int write, unsigned step, unsigned tetrad, unsigned reg,
|
||||
uint16_t data, uint32_t *address, uint16_t *value, uint8_t *command);
|
||||
|
||||
PCAN_ABI_API size_t pcan_abi_tms2812_build_memory(uint8_t controller, uint8_t command,
|
||||
uint32_t word_address, uint16_t value, uint8_t *output, size_t output_size);
|
||||
PCAN_ABI_API size_t pcan_abi_tms2812_build_test(uint8_t controller, uint16_t duration,
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include "balsam167_eeprom_layout.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
@@ -25,6 +26,46 @@ extern "C" {
|
||||
#define PERIPH28335_REQUEST_SIZE 8U
|
||||
#define PERIPH28335_COMMAND_COUNT 17U
|
||||
|
||||
/* Balsam-167 fitted AT25256B (marking 5EBL): 256 Kbit, NOT 256 Kbyte.
|
||||
* All reader positions below count physical EEPROM octets, not C28x words.
|
||||
* Four uint32_t fields make the caller-owned state stable for ctypes/JNI. */
|
||||
#define PERIPH28335_EEPROM_BYTES BALSAM167_EEPROM_BYTES
|
||||
typedef struct {
|
||||
uint32_t controller;
|
||||
uint32_t start;
|
||||
uint32_t length;
|
||||
uint32_t received;
|
||||
} periph28335_eeprom_reader;
|
||||
|
||||
/* No writes or capacity probes. Odd starts/lengths are allowed: the core reads
|
||||
* the containing 16-bit word and returns only the requested physical octets. */
|
||||
int periph28335_eeprom_begin(periph28335_eeprom_reader *reader,
|
||||
uint32_t controller, uint32_t start, uint32_t length);
|
||||
size_t periph28335_eeprom_request(const periph28335_eeprom_reader *reader,
|
||||
uint8_t *output, size_t capacity);
|
||||
/* Returns 1/2 decoded octets, or a negative status; errors do not advance state.
|
||||
* PEEK payload = little-endian data WORD + timer WORD, not four memory bytes.
|
||||
* SPI16 shifts the data word high-octet first, so physical EEPROM order is
|
||||
* response[3], response[2]. This differs from a C28x little-endian RAM dump. */
|
||||
int periph28335_eeprom_accept(periph28335_eeprom_reader *reader,
|
||||
const uint8_t *reply, size_t size, uint8_t *output, size_t capacity);
|
||||
|
||||
/* FC03 block reader uses the same state/physical byte range as legacy PEEK.
|
||||
* expected() is the exact response length. No state advance on CRC/size error. */
|
||||
size_t periph28335_eeprom_block_expected(const periph28335_eeprom_reader *r);
|
||||
size_t periph28335_eeprom_block_request(const periph28335_eeprom_reader *r, uint8_t *out, size_t cap);
|
||||
int periph28335_eeprom_block_accept(periph28335_eeprom_reader *r,
|
||||
const uint8_t *reply, size_t size, uint8_t *out, size_t cap);
|
||||
|
||||
/* Profile 0 = AT25256B, 1 = M95M02; stable additional API for every host port.
|
||||
* State remains four uint32_t fields. M95 requires block mode, no PEEK fallback. */
|
||||
uint32_t periph28335_eeprom_capacity_profile(unsigned profile);
|
||||
int periph28335_eeprom_begin_profile(periph28335_eeprom_reader *r, uint32_t c, uint32_t a, uint32_t n, unsigned profile);
|
||||
size_t periph28335_eeprom_block_expected_profile(const periph28335_eeprom_reader *r, unsigned profile);
|
||||
size_t periph28335_eeprom_block_request_profile(const periph28335_eeprom_reader *r, uint8_t *o, size_t n, unsigned profile);
|
||||
int periph28335_eeprom_block_accept_profile(periph28335_eeprom_reader *r, const uint8_t *p, size_t n, uint8_t *o, size_t cap, unsigned profile);
|
||||
size_t periph28335_eeprom_write_profile(uint32_t controller, uint32_t destination, uint32_t source, uint32_t length, unsigned profile, uint8_t *out, size_t cap);
|
||||
|
||||
typedef enum {
|
||||
PERIPH28335_OK = 0,
|
||||
PERIPH28335_ERROR_ARGUMENT = -1,
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define SET_SPECTRUM_MAX 16384U
|
||||
#define SET_SPECTRUM_MAX 2097152U
|
||||
enum { SET_WINDOW_RECT, SET_WINDOW_HANN, SET_WINDOW_HAMMING, SET_WINDOW_BLACKMAN, SET_WINDOW_FLATTOP };
|
||||
enum { SET_FILTER_NONE, SET_FILTER_LOW_PASS, SET_FILTER_HIGH_PASS, SET_FILTER_BAND_PASS, SET_FILTER_NOTCH };
|
||||
enum { SET_SPECTRUM_OK, SET_SPECTRUM_SHORT, SET_SPECTRUM_INVALID, SET_SPECTRUM_TIMING,
|
||||
|
||||
@@ -36,6 +36,9 @@ typedef enum {
|
||||
TMS2812_ERROR_CAPACITY = -6
|
||||
} tms2812_status_t;
|
||||
|
||||
int tms2812_bus_step(int write, unsigned step, unsigned tetrad, unsigned reg,
|
||||
uint16_t data, uint32_t *address, uint16_t *value, uint8_t *command);
|
||||
|
||||
uint16_t tms2812_crc16(const uint8_t *data, size_t size);
|
||||
|
||||
size_t tms2812_build_fixed_request(uint8_t controller, uint8_t command,
|
||||
|
||||
@@ -17,6 +17,10 @@ extern "C" {
|
||||
#define UMP_BASE 0x1000U
|
||||
#define UMP_DATA 0x1010U
|
||||
#define UMP_LIVE 0x1100U
|
||||
/* Read-only running firmware identity, same 12-word v1 contract as Climate.
|
||||
* Kept outside logger control/data and the EEPROM-backed legacy parameters. */
|
||||
#define UMP_FIRMWARE_INFO 0x1200U
|
||||
#define UMP_FIRMWARE_INFO_WORDS 12U
|
||||
#define UMP_MARKER 0x1F00U
|
||||
#define UMP_MAX_WORDS 124U
|
||||
#define UMP_RTU_CAPACITY 253U
|
||||
|
||||
@@ -8,7 +8,17 @@
|
||||
extern unsigned int ump_log_read(unsigned int address);
|
||||
extern int ump_log_write(unsigned int address, unsigned int value);
|
||||
static uint16_t read_word(void *context, uint16_t address)
|
||||
{ (void)context; return (uint16_t)ump_log_read(address); }
|
||||
{
|
||||
(void)context;
|
||||
/* Optional board binding to the common firmware-info static port. Older
|
||||
* consumers still link without it and return contract=0 (unknown version).
|
||||
* This hook changes neither PM67 CAN nor the saved parameter layout. */
|
||||
#ifdef UMP_FIRMWARE_INFO_READ
|
||||
if (address >= UMP_FIRMWARE_INFO && address < UMP_FIRMWARE_INFO + UMP_FIRMWARE_INFO_WORDS)
|
||||
return UMP_FIRMWARE_INFO_READ(address - UMP_FIRMWARE_INFO);
|
||||
#endif
|
||||
return (uint16_t)ump_log_read(address);
|
||||
}
|
||||
static int write_word(void *context, uint16_t address, uint16_t value)
|
||||
{ (void)context; return ump_log_write(address,value); }
|
||||
unsigned ump_tms28335_prepare(unsigned fn, uint16_t a, uint16_t n,
|
||||
|
||||
@@ -101,6 +101,17 @@ uint16_t pcan_abi_periph28335_crc16(const uint8_t *data, size_t size)
|
||||
return periph28335_crc16_modbus(data, size);
|
||||
}
|
||||
|
||||
int pcan_abi_periph28335_eeprom_begin(void *state,
|
||||
uint32_t controller, uint32_t start, uint32_t length)
|
||||
{ return periph28335_eeprom_begin((periph28335_eeprom_reader *)state, controller, start, length); }
|
||||
size_t pcan_abi_periph28335_eeprom_request(const void *state, uint8_t *out, size_t cap)
|
||||
{ return periph28335_eeprom_request((const periph28335_eeprom_reader *)state, out, cap); }
|
||||
int pcan_abi_periph28335_eeprom_accept(void *state, const uint8_t *reply,
|
||||
size_t size, uint8_t *out, size_t cap)
|
||||
{ return periph28335_eeprom_accept((periph28335_eeprom_reader *)state, reply, size, out, cap); }
|
||||
uint32_t pcan_abi_periph28335_eeprom_capacity(void)
|
||||
{ return PERIPH28335_EEPROM_BYTES; }
|
||||
|
||||
size_t pcan_abi_periph28335_append_crc(
|
||||
const uint8_t *payload, size_t payload_size,
|
||||
uint8_t *output, size_t output_size)
|
||||
@@ -368,3 +379,29 @@ int pcan_abi_gui_parser_stats(const void *parser_storage, uint32_t *frames,
|
||||
if (stray_bytes != NULL) *stray_bytes = parser->stats.stray_bytes;
|
||||
return 1;
|
||||
}
|
||||
|
||||
size_t pcan_abi_periph28335_eeprom_block_expected(const void *s)
|
||||
{ return periph28335_eeprom_block_expected((const periph28335_eeprom_reader *)s); }
|
||||
size_t pcan_abi_periph28335_eeprom_block_request(const void *s, uint8_t *out, size_t cap)
|
||||
{ return periph28335_eeprom_block_request((const periph28335_eeprom_reader *)s,out,cap); }
|
||||
int pcan_abi_periph28335_eeprom_block_accept(void *s, const uint8_t *reply, size_t size, uint8_t *out, size_t cap)
|
||||
{ return periph28335_eeprom_block_accept((periph28335_eeprom_reader *)s,reply,size,out,cap); }
|
||||
|
||||
uint32_t pcan_abi_periph28335_eeprom_capacity_profile(unsigned p)
|
||||
{ return periph28335_eeprom_capacity_profile(p); }
|
||||
int pcan_abi_periph28335_eeprom_begin_profile(void *s, uint32_t c, uint32_t a, uint32_t n, unsigned p)
|
||||
{ return periph28335_eeprom_begin_profile((periph28335_eeprom_reader *)s,c,a,n,p); }
|
||||
size_t pcan_abi_periph28335_eeprom_block_expected_profile(const void *s, unsigned p)
|
||||
{ return periph28335_eeprom_block_expected_profile((const periph28335_eeprom_reader *)s,p); }
|
||||
size_t pcan_abi_periph28335_eeprom_block_request_profile(const void *s, uint8_t *o, size_t n, unsigned p)
|
||||
{ return periph28335_eeprom_block_request_profile((const periph28335_eeprom_reader *)s,o,n,p); }
|
||||
int pcan_abi_periph28335_eeprom_block_accept_profile(void *s, const uint8_t *r, size_t n, uint8_t *o, size_t cap, unsigned p)
|
||||
{ return periph28335_eeprom_block_accept_profile((periph28335_eeprom_reader *)s,r,n,o,cap,p); }
|
||||
size_t pcan_abi_periph28335_eeprom_write_profile(uint32_t c, uint32_t d, uint32_t s, uint32_t n, unsigned p, uint8_t *o, size_t cap)
|
||||
{ return periph28335_eeprom_write_profile(c,d,s,n,p,o,cap); }
|
||||
|
||||
int pcan_abi_tms2812_bus_step(int write, unsigned step, unsigned tetrad, unsigned reg,
|
||||
uint16_t data, uint32_t *address, uint16_t *value, uint8_t *command)
|
||||
{
|
||||
return tms2812_bus_step(write, step, tetrad, reg, data, address, value, command);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,143 @@
|
||||
#include <string.h>
|
||||
|
||||
#include "set_crc.h"
|
||||
#include "balsam167_eeprom_block.h"
|
||||
|
||||
static int eeprom_reader_valid(const periph28335_eeprom_reader *r, unsigned profile)
|
||||
|
||||
{
|
||||
return r && r->controller >= 1U && r->controller <= 255U &&
|
||||
r->start < balsam167_eeprom_capacity(profile) && r->length &&
|
||||
r->length <= balsam167_eeprom_capacity(profile) - r->start && r->received < r->length;
|
||||
}
|
||||
|
||||
int periph28335_eeprom_begin_profile(periph28335_eeprom_reader *r,
|
||||
uint32_t controller, uint32_t start, uint32_t length, unsigned profile)
|
||||
{
|
||||
if (!r) return PERIPH28335_ERROR_ARGUMENT;
|
||||
/* Invalidate first: a rejected new range must not leave an old read active. */
|
||||
memset(r, 0, sizeof(*r));
|
||||
if (!controller || controller > 255U || start >= balsam167_eeprom_capacity(profile) ||
|
||||
!length || length > balsam167_eeprom_capacity(profile) - start)
|
||||
return PERIPH28335_ERROR_RANGE;
|
||||
r->controller = controller; r->start = start; r->length = length;
|
||||
return PERIPH28335_OK;
|
||||
}
|
||||
|
||||
size_t periph28335_eeprom_request(const periph28335_eeprom_reader *r,
|
||||
uint8_t *output, size_t capacity)
|
||||
{
|
||||
uint8_t payload[6];
|
||||
uint32_t word;
|
||||
if (!eeprom_reader_valid(r, 0)) return 0;
|
||||
word = (r->start + r->received) / 2U;
|
||||
payload[0] = (uint8_t)r->controller; payload[1] = 56U; /* CMD_PEEK only */
|
||||
/* 167 routes words 0..0x3fff to SPI EEPROM. The old terminal's
|
||||
* 0x10000000 memory selector is NOT implemented by this firmware. */
|
||||
payload[2] = (uint8_t)word; payload[3] = (uint8_t)(word >> 8);
|
||||
payload[4] = 0; payload[5] = 0;
|
||||
return periph28335_append_crc(payload, sizeof(payload), output, capacity);
|
||||
}
|
||||
|
||||
int periph28335_eeprom_accept(periph28335_eeprom_reader *r,
|
||||
const uint8_t *reply, size_t size, uint8_t *output, size_t capacity)
|
||||
{
|
||||
unsigned odd, count;
|
||||
if (!eeprom_reader_valid(r, 0) || !reply || !output) return PERIPH28335_ERROR_ARGUMENT;
|
||||
/* The UART layer may strip two legacy zero-padding bytes after CRC. */
|
||||
if (size != 8U && size != 10U) return PERIPH28335_ERROR_LENGTH;
|
||||
if (periph28335_crc16_modbus(reply, 8U) != 0) return PERIPH28335_ERROR_CRC;
|
||||
if (reply[0] != r->controller || reply[1] != 56U) return PERIPH28335_ERROR_HEADER;
|
||||
odd = (unsigned)((r->start + r->received) & 1U);
|
||||
count = 2U - odd;
|
||||
if (count > r->length - r->received) count = (unsigned)(r->length - r->received);
|
||||
if (capacity < count) return PERIPH28335_ERROR_CAPACITY;
|
||||
output[0] = reply[odd ? 2U : 3U];
|
||||
if (count == 2U) output[1] = reply[2];
|
||||
r->received += count;
|
||||
return (int)count;
|
||||
}
|
||||
|
||||
/* One common codec for all host ports. Physical EEPROM bytes already have
|
||||
* network-word order; only trim an odd first/last byte, never swap them. */
|
||||
static unsigned eeprom_block_words(const periph28335_eeprom_reader *r, unsigned profile)
|
||||
{
|
||||
uint32_t n;
|
||||
if (!eeprom_reader_valid(r, profile)) return 0;
|
||||
n = (r->length - r->received + ((r->start + r->received) & 1U) + 1U) / 2U;
|
||||
/* A response cannot cross a bank boundary; trim the last block there. */
|
||||
if (n > 32768U - ((r->start+r->received)/2U % 32768U))
|
||||
n = 32768U - ((r->start+r->received)/2U % 32768U);
|
||||
return (unsigned)(n > BALSAM167_EEPROM_BLOCK_WORDS ? BALSAM167_EEPROM_BLOCK_WORDS : n);
|
||||
}
|
||||
size_t periph28335_eeprom_block_expected_profile(const periph28335_eeprom_reader *r, unsigned profile)
|
||||
{
|
||||
unsigned n = eeprom_block_words(r, profile);
|
||||
return n ? 5U + 2U*n : 0;
|
||||
}
|
||||
size_t periph28335_eeprom_block_request_profile(const periph28335_eeprom_reader *r, uint8_t *out, size_t cap, unsigned profile)
|
||||
{
|
||||
uint8_t p[6];
|
||||
unsigned n = eeprom_block_words(r, profile), a, quantity;
|
||||
uint32_t word;
|
||||
if (!n) return 0;
|
||||
word = (r->start + r->received) / 2U;
|
||||
a = 0x8000U + (unsigned)(word % 32768U);
|
||||
quantity = n | (profile ? (0x8000U | (unsigned)(word/32768U)*256U) : 0U);
|
||||
if (!balsam167_eeprom_block_valid_profile(3,a,quantity,profile)) return 0;
|
||||
p[0]=(uint8_t)r->controller; p[1]=3;
|
||||
p[2]=(uint8_t)(a>>8); p[3]=(uint8_t)a; p[4]=(uint8_t)(quantity>>8); p[5]=(uint8_t)quantity;
|
||||
return periph28335_append_crc(p,6,out,cap);
|
||||
}
|
||||
int periph28335_eeprom_block_accept_profile(periph28335_eeprom_reader *r,
|
||||
const uint8_t *reply, size_t size, uint8_t *out, size_t cap, unsigned profile)
|
||||
{
|
||||
unsigned n=eeprom_block_words(r, profile), odd, count;
|
||||
if (!n || !reply || !out) return PERIPH28335_ERROR_ARGUMENT;
|
||||
if (size != 5U+2U*n) return PERIPH28335_ERROR_LENGTH;
|
||||
if (periph28335_crc16_modbus(reply,size)) return PERIPH28335_ERROR_CRC;
|
||||
/* Common address 16 is answered by the board's own Mode, 5 or 6. */
|
||||
if ((reply[0] != r->controller && !(r->controller==16 && (reply[0]==5 || reply[0]==6))) ||
|
||||
reply[1]!=3 || reply[2]!=2U*n) return PERIPH28335_ERROR_HEADER;
|
||||
odd=(unsigned)((r->start+r->received)&1U);
|
||||
count=2U*n-odd;
|
||||
if (count>r->length-r->received) count=(unsigned)(r->length-r->received);
|
||||
if (cap<count) return PERIPH28335_ERROR_CAPACITY;
|
||||
memcpy(out,reply+3+odd,count);
|
||||
r->received+=count;
|
||||
return (int)count;
|
||||
}
|
||||
|
||||
/* Keep all existing exported symbols and the 16-byte reader ABI unchanged. */
|
||||
int periph28335_eeprom_begin(periph28335_eeprom_reader *r, uint32_t c, uint32_t a, uint32_t n)
|
||||
{ return periph28335_eeprom_begin_profile(r,c,a,n,0); }
|
||||
size_t periph28335_eeprom_block_expected(const periph28335_eeprom_reader *r)
|
||||
{ return periph28335_eeprom_block_expected_profile(r,0); }
|
||||
size_t periph28335_eeprom_block_request(const periph28335_eeprom_reader *r, uint8_t *o, size_t n)
|
||||
{ return periph28335_eeprom_block_request_profile(r,o,n,0); }
|
||||
int periph28335_eeprom_block_accept(periph28335_eeprom_reader *r, const uint8_t *p, size_t n, uint8_t *o, size_t cap)
|
||||
{ return periph28335_eeprom_block_accept_profile(r,p,n,o,cap,0); }
|
||||
uint32_t periph28335_eeprom_capacity_profile(unsigned profile)
|
||||
{ return (uint32_t)balsam167_eeprom_capacity(profile); }
|
||||
/* Legacy EXTEND carries word addresses and octet lengths. A distinct M95
|
||||
* subcommand makes a wrong-profile write fail closed on either firmware. */
|
||||
size_t periph28335_eeprom_write_profile(uint32_t controller, uint32_t destination,
|
||||
uint32_t source, uint32_t length, unsigned profile, uint8_t *out, size_t cap)
|
||||
{
|
||||
uint8_t p[16]; unsigned i;
|
||||
uint32_t capacity = periph28335_eeprom_capacity_profile(profile);
|
||||
uint32_t limit = capacity ? capacity-(BALSAM167_EEPROM_PARAM_WORDS+1U)*2U : 0;
|
||||
if (!controller || controller>255U || !length || length>199993U ||
|
||||
destination>=limit/2U || length>limit-destination*2U) return 0;
|
||||
p[0]=(uint8_t)controller; p[1]=60;
|
||||
for(i=0;i<4;i++) {
|
||||
p[2+i]=(uint8_t)(destination>>(8*i));
|
||||
p[6+i]=(uint8_t)(source>>(8*i));
|
||||
p[10+i]=(uint8_t)(length>>(8*i));
|
||||
}
|
||||
p[14]=(uint8_t)(profile ? BALSAM167_EEPROM_WRITE_M95 : 4); p[15]=0;
|
||||
return periph28335_append_crc(p,16,out,cap);
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
const char *project;
|
||||
|
||||
@@ -137,3 +137,16 @@ int tms2812_decode_upload_response(
|
||||
memcpy(output, &data[2], byte_count);
|
||||
return TMS2812_OK;
|
||||
}
|
||||
|
||||
int tms2812_bus_step(int write, unsigned step, unsigned tetrad, unsigned reg,
|
||||
uint16_t data, uint32_t *address, uint16_t *value, uint8_t *command)
|
||||
{
|
||||
if (!address || !value || !command) return TMS2812_ERROR_ARGUMENT;
|
||||
if ((write != 0 && write != 1) || step > 1U || tetrad > 14U || reg > 15U)
|
||||
return TMS2812_ERROR_RANGE;
|
||||
*command = (!write && step == 1U) ? TMS2812_CMD_PEEK : TMS2812_CMD_POKE;
|
||||
*address = write ? (step == 0U ? 0x200AU : 0x200BU) : (step == 0U ? 0x200BU : 0x200FU);
|
||||
*value = write && step == 0U ? data : (!write && step == 1U ? 0U :
|
||||
(uint16_t)((write ? 0x8000U : 0U) | (tetrad << 4) | reg));
|
||||
return TMS2812_OK;
|
||||
}
|
||||
|
||||
@@ -12,6 +12,11 @@ unsigned ump_validate(unsigned fn, uint16_t a, uint16_t n, int can)
|
||||
{
|
||||
if (fn == 6) return a >= UMP_BASE && a <= UMP_BASE+3 ? 0 : 2;
|
||||
if (fn != 3) return 1;
|
||||
/* CAN reads the whole immutable block (six fragments); RTU may read slices.
|
||||
* Check subtraction only after the lower bound, avoiding uint16 wraparound. */
|
||||
if (a >= UMP_FIRMWARE_INFO && a < UMP_FIRMWARE_INFO + UMP_FIRMWARE_INFO_WORDS)
|
||||
return can ? ((a == UMP_FIRMWARE_INFO && n == UMP_FIRMWARE_INFO_WORDS) ? 0 : 2)
|
||||
: ((n && n <= UMP_FIRMWARE_INFO + UMP_FIRMWARE_INFO_WORDS - a) ? 0 : 2);
|
||||
if (can) return ((a == UMP_BASE && n == 16) ||
|
||||
(a == UMP_DATA && (n == 40 || n == 80 || n == 120 || n == 42 || n == 84)) ||
|
||||
(a == UMP_LIVE && n == 124)) ? 0 : 2;
|
||||
|
||||
156
c/set-protocol/tests/test_periph28335_eeprom.c
Normal file
156
c/set-protocol/tests/test_periph28335_eeprom.c
Normal file
@@ -0,0 +1,156 @@
|
||||
#include "periph28335.h"
|
||||
#include <assert.h>
|
||||
#include "balsam167_eeprom_block.h"
|
||||
#include <string.h>
|
||||
|
||||
static void response(uint8_t *out, unsigned high, unsigned low)
|
||||
{
|
||||
/* Reply is low data byte, high data byte, then an unrelated timer.
|
||||
* Nonzero timer detects accidentally copying four memory bytes per PEEK. */
|
||||
uint8_t payload[] = {16,56,0,0,0xef,0xbe};
|
||||
payload[2]=(uint8_t)low; payload[3]=(uint8_t)high;
|
||||
assert(periph28335_append_crc(payload,6,out,10)==8);
|
||||
out[8]=out[9]=0;
|
||||
}
|
||||
|
||||
static void blocks(void)
|
||||
{
|
||||
periph28335_eeprom_reader r;
|
||||
uint8_t req[8], reply[245], out[240];
|
||||
unsigned start, transactions, n, a, i, old;
|
||||
int got;
|
||||
assert(!balsam167_eeprom_block_valid(6,0x8000,1));
|
||||
assert(!balsam167_eeprom_block_valid(3,0xbfff,2));
|
||||
assert(!balsam167_eeprom_block_valid(3,0xffff,120));
|
||||
assert(!balsam167_eeprom_block_valid(3,0x8000,121));
|
||||
assert(!balsam167_eeprom_block_valid(3,0x8000,0));
|
||||
assert(balsam167_eeprom_block_valid(3,0xbfff,1));
|
||||
for(start=0;start<2;start++) {
|
||||
assert(!periph28335_eeprom_begin(&r,16,start,32768-start));
|
||||
transactions=0;
|
||||
while(r.received<r.length) {
|
||||
assert(periph28335_eeprom_block_request(&r,req,8)==8);
|
||||
assert(!periph28335_crc16_modbus(req,8));
|
||||
a=((unsigned)req[2]*256+req[3]-0x8000)*2;
|
||||
n=req[5]; assert(n && n<=120 && a+2*n<=32768);
|
||||
reply[0]=5; reply[1]=3; reply[2]=(uint8_t)(2*n);
|
||||
for(i=0;i<2*n;i++) reply[3+i]=(uint8_t)(a+i);
|
||||
assert(periph28335_append_crc(reply,3+2*n,reply,sizeof reply)==5+2*n);
|
||||
old=r.received;
|
||||
reply[4]^=1;
|
||||
assert(periph28335_eeprom_block_accept(&r,reply,5+2*n,out,240)==PERIPH28335_ERROR_CRC);
|
||||
assert(r.received==old); reply[4]^=1;
|
||||
assert(periph28335_eeprom_block_accept(&r,reply,5+2*n,out,0)==PERIPH28335_ERROR_CAPACITY);
|
||||
assert(r.received==old);
|
||||
got=periph28335_eeprom_block_accept(&r,reply,5+2*n,out,240);
|
||||
assert(got>0);
|
||||
for(i=0;i<(unsigned)got;i++) assert(out[i]==(uint8_t)(start+old+i));
|
||||
transactions++;
|
||||
}
|
||||
assert(transactions==137);
|
||||
assert(!periph28335_eeprom_block_request(&r,req,8));
|
||||
}
|
||||
}
|
||||
|
||||
static void profiles(void)
|
||||
{
|
||||
periph28335_eeprom_reader r;
|
||||
uint8_t req[18], reply[245], out[240];
|
||||
unsigned n, i, q, a, bankmask=0;
|
||||
uint32_t pos, before;
|
||||
assert(periph28335_eeprom_capacity_profile(0)==32768);
|
||||
assert(periph28335_eeprom_capacity_profile(1)==262144);
|
||||
assert(periph28335_eeprom_capacity_profile(2)==0);
|
||||
assert(periph28335_eeprom_begin_profile(&r,16,262143,2,1)<0);
|
||||
assert(periph28335_eeprom_begin_profile(&r,16,0,2,2)<0);
|
||||
assert(!balsam167_eeprom_block_valid_profile(3,0xffff,0x8078,1));
|
||||
assert(!balsam167_eeprom_block_valid_profile(3,0x8000,0x8401,1));
|
||||
assert(!balsam167_eeprom_block_valid_profile(3,0x8000,0x8001,0));
|
||||
assert(!balsam167_eeprom_block_valid_profile(3,0x8000,1,1));
|
||||
assert(balsam167_eeprom_block_valid_profile(3,0xffff,0x8301,1));
|
||||
/* Full 256-KiB dump from an odd start exercises every bank transition,
|
||||
* first/last byte trimming, CRC failure and exact physical byte order. */
|
||||
assert(!periph28335_eeprom_begin_profile(&r,16,1,262143,1));
|
||||
while(r.received<r.length) {
|
||||
assert(periph28335_eeprom_block_request_profile(&r,req,8,1)==8);
|
||||
assert(!periph28335_crc16_modbus(req,8));
|
||||
a=req[2]*256U+req[3]; q=req[4]*256U+req[5]; n=q&255U;
|
||||
assert(balsam167_eeprom_block_valid_profile(3,a,q,1));
|
||||
pos=balsam167_eeprom_block_word(a,q)*2UL;
|
||||
assert(pos == (r.start+r.received)/2*2);
|
||||
bankmask |= 1U << ((q>>8)&3U);
|
||||
assert(periph28335_eeprom_block_expected_profile(&r,1)==5+2*n);
|
||||
reply[0]=5; reply[1]=3; reply[2]=(uint8_t)(n*2);
|
||||
for(i=0;i<n*2;i++) reply[3+i]=(uint8_t)((pos+i)^((pos+i)>>16));
|
||||
periph28335_append_crc(reply,3+n*2,reply,sizeof(reply));
|
||||
before=r.received; reply[4]^=1;
|
||||
assert(periph28335_eeprom_block_accept_profile(&r,reply,5+2*n,out,240,1)==PERIPH28335_ERROR_CRC);
|
||||
assert(r.received==before); reply[4]^=1;
|
||||
assert(periph28335_eeprom_block_accept_profile(&r,reply,5+2*n,out,240,1)>0);
|
||||
for(i=0;i<r.received-before;i++) {
|
||||
pos=1+before+i;
|
||||
assert(out[i]==(uint8_t)(pos^(pos>>16)));
|
||||
}
|
||||
}
|
||||
assert(bankmask==15);
|
||||
assert(!periph28335_eeprom_block_request_profile(&r,req,8,1));
|
||||
assert(periph28335_eeprom_write_profile(5,0,0x200000,33514,0,req,18)==0);
|
||||
assert(periph28335_eeprom_write_profile(5,0,0x200000,33514,1,req,18)==18);
|
||||
assert(req[14]==0x44 && req[15]==0 && !periph28335_crc16_modbus(req,18));
|
||||
assert(!periph28335_eeprom_write_profile(5,0x1ff7e,0,2,1,req,18));
|
||||
assert(!periph28335_eeprom_write_profile(5,0xffffffff,0,2,1,req,18));
|
||||
assert(periph28335_eeprom_write_profile(5,0,0,32438,0,req,18)==18 && req[14]==4);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
periph28335_eeprom_reader reader;
|
||||
blocks();
|
||||
profiles();
|
||||
uint8_t request[8], reply[10], data[2];
|
||||
unsigned i;
|
||||
assert(sizeof(reader)==16);
|
||||
assert(periph28335_eeprom_begin(&reader,16,0,0)==PERIPH28335_ERROR_RANGE);
|
||||
assert(periph28335_eeprom_begin(&reader,0,0,2)==PERIPH28335_ERROR_RANGE);
|
||||
assert(periph28335_eeprom_begin(&reader,16,0x8000,1)==PERIPH28335_ERROR_RANGE);
|
||||
assert(periph28335_eeprom_begin(&reader,16,0x7fff,2)==PERIPH28335_ERROR_RANGE);
|
||||
assert(periph28335_eeprom_begin(&reader,16,1,0xffffffffUL)==PERIPH28335_ERROR_RANGE);
|
||||
assert(periph28335_eeprom_request(&reader,request,8)==0);
|
||||
assert(periph28335_eeprom_begin(&reader,16,1,3)==0);
|
||||
assert(periph28335_eeprom_request(&reader,request,7)==0);
|
||||
assert(periph28335_eeprom_request(&reader,request,8)==8);
|
||||
assert(memcmp(request,"\x10\x38\0\0\0\0",6)==0);
|
||||
response(reply,0x12,0x34);
|
||||
assert(periph28335_eeprom_accept(&reader,reply,8,data,2)==1 && data[0]==0x34);
|
||||
assert(reader.received==1);
|
||||
assert(periph28335_eeprom_request(&reader,request,8)==8 && request[2]==1);
|
||||
reply[2]^=1;
|
||||
assert(periph28335_eeprom_accept(&reader,reply,8,data,2)==PERIPH28335_ERROR_CRC);
|
||||
assert(reader.received==1);
|
||||
response(reply,0x56,0x78);
|
||||
assert(periph28335_eeprom_accept(&reader,reply,8,data,1)==PERIPH28335_ERROR_CAPACITY);
|
||||
assert(reader.received==1);
|
||||
assert(periph28335_eeprom_accept(&reader,reply,10,data,2)==2);
|
||||
assert(data[0]==0x56 && data[1]==0x78 && reader.received==3);
|
||||
assert(periph28335_eeprom_request(&reader,request,8)==0);
|
||||
|
||||
/* Every requested word of a complete dump remains in the EEPROM window.
|
||||
* In particular, neither the RAM route nor selector 0x10000000 is used. */
|
||||
assert(periph28335_eeprom_begin(&reader,16,0,32768)==0);
|
||||
for (i=0;i<16384;i++) {
|
||||
assert(periph28335_eeprom_request(&reader,request,8)==8);
|
||||
assert(request[1]==56 && request[4]==0 && request[5]==0);
|
||||
assert(((unsigned)request[3]*256+request[2])==i);
|
||||
assert(periph28335_crc16_modbus(request,8)==0);
|
||||
response(reply,i>>8,i);
|
||||
assert(periph28335_eeprom_accept(&reader,reply,8,data,2)==2);
|
||||
assert(data[0]==(i>>8) && data[1]==(i&255));
|
||||
}
|
||||
assert(reader.received==32768 && periph28335_eeprom_request(&reader,request,8)==0);
|
||||
assert(periph28335_eeprom_begin(&reader,16,0x7fff,1)==0);
|
||||
assert(periph28335_eeprom_request(&reader,request,8)==8);
|
||||
assert(request[2]==0xff && request[3]==0x3f);
|
||||
response(reply,0x9a,0xbc);
|
||||
assert(periph28335_eeprom_accept(&reader,reply,8,data,2)==1 && data[0]==0xbc);
|
||||
return 0;
|
||||
}
|
||||
@@ -18,6 +18,19 @@ static int test_terminal(void)
|
||||
uint8_t reply[] = {0x0a, 0x38, 0x65, 0xa5, 0, 0, 0xaf, 0x9b, 0, 0};
|
||||
const uint8_t test_prefix[] = {7, 66, 25, 0, 18, 0};
|
||||
size_t size;
|
||||
uint32_t bus_address;
|
||||
uint16_t bus_value;
|
||||
uint8_t bus_command;
|
||||
if (tms2812_bus_step(1, 0, 3, 5, 0x1234, &bus_address, &bus_value, &bus_command) ||
|
||||
bus_address != 0x200AU || bus_value != 0x1234 || bus_command != 57) return 20;
|
||||
if (tms2812_bus_step(1, 1, 3, 5, 0x1234, &bus_address, &bus_value, &bus_command) ||
|
||||
bus_address != 0x200BU || bus_value != 0x8035 || bus_command != 57) return 21;
|
||||
if (tms2812_bus_step(0, 0, 3, 5, 0, &bus_address, &bus_value, &bus_command) ||
|
||||
bus_address != 0x200BU || bus_value != 0x35 || bus_command != 57) return 22;
|
||||
if (tms2812_bus_step(0, 1, 3, 5, 0, &bus_address, &bus_value, &bus_command) ||
|
||||
bus_address != 0x200FU || bus_command != 56) return 23;
|
||||
if (tms2812_bus_step(0, 0, 15, 0, 0, &bus_address, &bus_value, &bus_command) != TMS2812_ERROR_RANGE ||
|
||||
tms2812_bus_step(0, 2, 0, 0, 0, &bus_address, &bus_value, &bus_command) != TMS2812_ERROR_RANGE) return 24;
|
||||
if (tms2812_build_memory_request(10, 0, 0, 0, output, sizeof output) != 8U ||
|
||||
memcmp(output, ping, sizeof ping) != 0) return 10;
|
||||
if (tms2812_build_memory_request(10, TMS2812_CMD_POKE, 0x12345678UL, 0xABCD,
|
||||
|
||||
Reference in New Issue
Block a user