Compare commits
6
Commits
device-tools
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
28fe22439c | ||
|
|
a197437985 | ||
|
|
ae018a1347 | ||
|
|
80d8fe1794 | ||
|
|
d057df7698 | ||
|
|
f9d369d032 |
@@ -4,3 +4,8 @@
|
||||
|
||||
*.pdf binary
|
||||
*.png binary
|
||||
|
||||
# CMD.exe требует CRLF в BAT, иначе после переноса Git часть команд разбирается неверно.
|
||||
*.bat text eol=crlf
|
||||
# Командные файлы CMD выполняются тем же интерпретатором Windows и требуют CRLF.
|
||||
*.cmd text eol=crlf
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
# Загрузчик F28335 по RS для periph_28335
|
||||
|
||||
Постоянный загрузчик принимает `INITLOAD` (58), `LOAD` (51) и `TFLASH` (55),
|
||||
совместимые с Flash RS в терминале `periph_28335` SETGUI. Ядро не зависит от
|
||||
UART, GPIO, ОС и Qt. Оно использует существующий валидатор и writer
|
||||
`c/protocan-boot/ports/tms320f28335`, сохраняя единый формат SCI8/FAB1.
|
||||
|
||||
Слои: клиент periph_28335 → автомат RS → проверка SCI8/FAB1 → callbacks платы
|
||||
→ TI Flash API. Передаются октеты; адреса CPU считаются 16-битными словами.
|
||||
|
||||
| Файл | Назначение и зависимости |
|
||||
|---|---|
|
||||
| `include/f28335_rs_boot.h`, `src/f28335_rs_boot.c` | Переносимый автомат, зависит от protocan-boot |
|
||||
| `ports/tms320f28335/flash_api.c` | Официальный TI API v2.10, заголовки устройства, частота F28335_RS_CPU_HZ |
|
||||
| `ports/tms320f28335/boot.cmd` | Постоянный сектор A, отдельные RAMAPI, RAMFUNCS и стек |
|
||||
| `tools/build.py` | Сборка boot и выпуск проверенных Flash OUT/SCI8 с manifest/SHA-256 |
|
||||
| `tests/test_boot.py` | Настоящий автомат и writer в модели RAM/Flash без оборудования |
|
||||
|
||||
Приложение занимает `[0x300000,0x338000)`, первые 16 слов содержат FAB1.
|
||||
Сектор A и переход `0x33FFF6` принадлежат постоянному загрузчику.
|
||||
`INITLOAD` разрешает только staging `0x200000`, чётный размер 24..130560
|
||||
октетов. Ошибочные CRC, таймаут и ошибка UART отзывают подтверждение LOAD.
|
||||
Перед erase повторно проверяются RAM, CRC32, все адреса, entry и FAB1.
|
||||
Общий writer пишет FAB1 последним и проверяет физическую Flash.
|
||||
Ошибка не удаляет recovery; перезапуск разрешён после успешного ACK.
|
||||
|
||||
Контракт callbacks: `stage_write(user, offset, octet)` и `stage.read(user,
|
||||
offset, buffer, size)` обслуживают RAM относительно начала файла;
|
||||
`transmit(user, data, size)` возвращает true после физического окончания TX;
|
||||
`program(user, image)` вызывает общий Flash writer; `reboot(user)` сбрасывает
|
||||
контроллер. Все чтения/записи должны завершиться полностью либо вернуть false.
|
||||
Прерывания в постоянном загрузчике запрещены, watchdog выключен на время
|
||||
стирания. Межбайтовый таймаут аппаратный порт передаёт в `f28335_rs_abort`.
|
||||
|
||||
Быстрый старт: выделить `f28335_rs_boot_t` в RAM, заполнить пять callbacks,
|
||||
вызвать `f28335_rs_init(&boot, &port, 16)` и передавать каждый октет SCI
|
||||
через `f28335_rs_feed(&boot, octet)`. До запуска приложения вызвать
|
||||
`f28335_flash_app_validate`, после успеха перейти на возвращённый entry.
|
||||
Аппаратные таймауты, выключение силовых выходов и выбор скорости задаёт плата.
|
||||
|
||||
Подключено в `prolitarski_ups`: SCI-A/RS-422, 115200 8N1, адрес 16;
|
||||
Monitor №93=1 в остановленном состоянии запрашивает boot через watchdog.
|
||||
Первичная установка требует записи двух OUT через JTAG с сохранением
|
||||
сектора A при загрузке приложения. Все последующие RS-обновления меняют B–H.
|
||||
Повреждённое приложение оставляет устройство в RS recovery после сброса.
|
||||
@@ -0,0 +1,23 @@
|
||||
#ifndef F28335_RS_BOOT_H /* Один контракт обслуживает постоянный загрузчик и его модель на ПК. */
|
||||
#define F28335_RS_BOOT_H /* Исключает повторные объявления при подключении аппаратного порта. */
|
||||
#include "../../protocan-boot/ports/tms320f28335/f28335_flash_update.h" /* Повторно использует проверку SCI8/FAB1 и запись с защитой сектора A. */
|
||||
#define F28335_RS_STAGE 0x200000UL /* Адрес слова внешней RAM совпадает с INITLOAD в periph_28335. */
|
||||
#define F28335_RS_LIMIT F28335_AB_SLOT_BYTES /* Проверяемый SCI8 ограничен 130560 октетами общего валидатора F28335. */
|
||||
typedef struct { /* Аппаратная плата владеет памятью и UART; ядро владеет только состоянием протокола. */
|
||||
bool (*stage_write)(void *, uint32_t, unsigned int); /* Записывает один октет по относительному смещению staging RAM. */
|
||||
f28335_boot_io_t stage; /* Читает октеты staging для повторной CRC32 и проверки всех адресов перед erase. */
|
||||
bool (*transmit)(void *, const unsigned int *, unsigned int); /* Передаёт полный ответ и дожидается последнего стопового бита. */
|
||||
unsigned int (*program)(void *, f28335_flash_update_t *); /* Возвращает 0 после записи и проверки, код TI либо 1004/1006 при защите или ошибке проверки; управление остаётся в секторе A. */
|
||||
void (*reboot)(void *); /* Выполняет reset только после успешного ответа о завершении записи. */
|
||||
void *user; /* Независимый контекст UART, Flash и модели испытаний. */
|
||||
} f28335_rs_port_t; /* Аппаратный порт не собирает кадры и не дублирует алгоритм проверки образа. */
|
||||
typedef struct { /* Экземпляр допускает работу без heap и без прерываний. */
|
||||
f28335_rs_port_t port; /* Сохраняет callbacks на весь сеанс обновления. */
|
||||
f28335_flash_update_t image; /* Ограниченный индекс адресных секций и страничный кеш общего валидатора. */
|
||||
uint32_t length, position, expected, received, crc32; /* Длины и позиции считаются октетами, независимо от sizeof(char) C28x. */
|
||||
unsigned int address, command, crc16, wire_crc, loaded, fault, header[16]; /* Заголовок вмещает максимальный фиксированный запрос TFLASH на 16 октетов. */
|
||||
} f28335_rs_boot_t; /* Загруженный блок никогда не исполняется как произвольный код RAM. */
|
||||
void f28335_rs_init(f28335_rs_boot_t *s, const f28335_rs_port_t *port, unsigned int address); /* Настраивает адрес 1..255 и сбрасывает разрешение программирования. */
|
||||
void f28335_rs_abort(f28335_rs_boot_t *s); /* Сбрасывает неполный кадр и подтверждение LOAD при таймауте или аппаратной ошибке SCI. */
|
||||
void f28335_rs_feed(f28335_rs_boot_t *s, unsigned int value); /* Принимает один восьмибитный октет совместимых INITLOAD/LOAD/TFLASH. */
|
||||
#endif /* Завершает публичный интерфейс общего RS-загрузчика. */
|
||||
@@ -0,0 +1,28 @@
|
||||
MEMORY /* Все адреса и размеры относятся к 16-битным словам CPU C28x. */
|
||||
{ /* Постоянный загрузчик использует только сектор A и внутреннюю RAM. */
|
||||
PAGE 0: /* Исполняемый код и адреса загрузки из Flash. */
|
||||
FLASHA : origin = 0x338000, length = 0x007F80 /* Область постоянного boot заканчивается до служебной области CSM. */
|
||||
BEGIN : origin = 0x33FFF6, length = 0x000002 /* Два слова аппаратного перехода Boot ROM в режиме Flash. */
|
||||
RAMAPI : origin = 0x008000, length = 0x001800 /* 6144 слова предназначены для копии официального TI Flash API. */
|
||||
RAMFUNCS : origin = 0x00F800, length = 0x000800 /* InitFlash выполняется из RAM, не затрагивая код самого API. */
|
||||
RESET : origin = 0x3FFFC0, length = 0x000002 /* Вектор reset находится в ROM и не программируется. */
|
||||
PAGE 1: /* Данные загрузчика не зависят от размещения будущего приложения. */
|
||||
RAMM0 : origin = 0x000058, length = 0x0003A8 /* Слова 50..57 сохранены для запроса boot через watchdog reset. */
|
||||
RAMM1 : origin = 0x000400, length = 0x000400 /* Стек на 1024 слова вмещает буфер writer и контекст runtime. */
|
||||
RAMDATA : origin = 0x00E000, length = 0x001800 /* Индекс SCI8, состояния протокола и аппаратные данные находятся в RAM. */
|
||||
} /* Внешняя RAM используется через callbacks и не содержит исполняемого кода. */
|
||||
SECTIONS /* C runtime загрузчика, в отличие от приложения, целиком сохраняется в A. */
|
||||
{ /* Размещение API указано до общей .text, чтобы извлечь все его код и константы. */
|
||||
codestart : > BEGIN, PAGE = 0 /* Штатный TI startup запрещает watchdog и вызывает _c_int00 загрузчика. */
|
||||
flash_api : { -lFlash28335_API_V210.lib(.text) -lFlash28335_API_V210.lib(.econst) } LOAD = FLASHA, RUN = RAMAPI, LOAD_START(_FlashApiLoadStart), LOAD_END(_FlashApiLoadEnd), RUN_START(_FlashApiRunStart), PAGE = 0 /* Ни одна функция TI API не исполняется из занятого банка Flash. */
|
||||
.text : > FLASHA, PAGE = 0 /* Протокол, валидатор и собственный runtime загрузчика остаются после обновления приложения. */
|
||||
.cinit : > FLASHA, PAGE = 0 /* Инициализация данных не зависит от целостности B–H. */
|
||||
.pinit : > FLASHA, PAGE = 0 /* Поддерживает стандартную секцию инициализации COFF. */
|
||||
.econst : > FLASHA, PAGE = 0 /* Таблицы и константы загрузчика также защищены от erase B–H. */
|
||||
.switch : > FLASHA, PAGE = 0 /* Таблицы переходов автомата не находятся в заменяемом приложении. */
|
||||
ramfuncs : LOAD = FLASHA, RUN = RAMFUNCS, LOAD_START(_RamfuncsLoadStart), LOAD_END(_RamfuncsLoadEnd), RUN_START(_RamfuncsRunStart), PAGE = 0 /* InitFlash переносится до изменения Flash wait states. */
|
||||
.stack : > RAMM1, PAGE = 1 /* Стек отделён от handoff и буфера внешней RAM. */
|
||||
.ebss : > RAMDATA, PAGE = 1 /* Нулевая инициализация касается только состояния boot. */
|
||||
.esysmem : > RAMDATA, PAGE = 1 /* Оставляет корректную секцию runtime без использования heap в алгоритме. */
|
||||
.reset : > RESET, PAGE = 0, TYPE = DSECT /* ROM-вектор не попадает в программируемый OUT. */
|
||||
} /* Пароли CSM, OTP и сектора приложения не создаются в образе загрузчика. */
|
||||
@@ -0,0 +1,33 @@
|
||||
#include "DSP2833x_Device.h" /* Использует официальные описания регистров выбранной платы F28335. */
|
||||
#include "../../include/f28335_rs_boot.h" /* Получает общий writer с повторной проверкой staging перед erase. */
|
||||
#include "Flash2833x_API_Library.h" /* Официальный TI API v2.10 выполняет физические операции внутренней Flash. */
|
||||
#include <string.h> /* Копирует код и таблицы TI API в RAM до изменения общего банка Flash. */
|
||||
extern Uint16 FlashApiLoadStart, FlashApiLoadEnd, FlashApiRunStart; /* LOAD/RUN-символы создаёт linker постоянного загрузчика. */
|
||||
static FLASH_ST api_status; /* Диагностика TI хранится в RAM и доступна во время занятости Flash. */
|
||||
static unsigned int api_error; /* Код последней операции TI передаётся клиенту без подмены успехом. */
|
||||
static bool erase(void *user) /* Стирает исключительно семь секторов приложения B–H. */
|
||||
{ /* Сам API исполняется из RAM; вызывающий код продолжит работу из сохранённого сектора A. */
|
||||
(void)user; api_error = Flash_Erase(0x00FEU, &api_status); return api_error == STATUS_SUCCESS; /* В TI v2.10 бит 0 выбирает A, маска FE исключает его и сохраняет recovery; результат сохраняет диагностику API. */
|
||||
} /* Ненулевой результат запрещает программирование данных. */
|
||||
static bool program(void *user, uint32_t address, uint16_t *data, unsigned int words) /* Записывает и немедленно проверяет одну ограниченную порцию. */
|
||||
{ /* Дополнительная аппаратная проверка диапазона защищает boot независимо от wire-парсера. */
|
||||
Uint16 result; (void)user; /* Результат официального API не подменяется успешным кодом протокола. */
|
||||
if (!words || address < F28335_FLASH_APP_START || address >= F28335_FLASH_APP_STOP || words > F28335_FLASH_APP_STOP - address) return false; /* Исключает сектор A, CSM, OTP и переполнение верхней границы. */
|
||||
result = Flash_Program((Uint16 *)address, data, words, &api_status); /* Данные и рабочий буфер находятся в RAM. */
|
||||
if (!result) result = Flash_Verify((Uint16 *)address, data, words, &api_status); /* Подтверждает физическую запись до перехода к следующей порции. */
|
||||
api_error = result; return result == STATUS_SUCCESS; /* Ошибка оставляет FAB1 неопубликованным, сохраняет код TI и RS recovery. */
|
||||
} /* Все прерывания остаются запрещёнными на протяжении жизни постоянного загрузчика. */
|
||||
uint16_t F28335Rs_ReadFlash(void *user, uint32_t address) /* Читает физическое слово для общей проверки приложения перед запуском. */
|
||||
{ /* Общий валидатор ограничивает адреса диапазоном B–H. */
|
||||
(void)user; return *(volatile Uint16 *)address; /* Чтение применяется только вне активной операции Flash API. */
|
||||
} /* Стёртые промежутки проверяются вместе с загруженными секциями. */
|
||||
unsigned int F28335Rs_Program(void *user, f28335_flash_update_t *image) /* Callback работает из сектора A и возвращает полный статус, совместимый с Flash RS periph_28335. */
|
||||
{ /* Проект задаёт реальный SYSCLK в герцах через F28335_RS_CPU_HZ при компиляции. */
|
||||
f28335_flash_ops_t ops; (void)user; /* Таблица операций не содержит обращений к приложению из B–H. */
|
||||
if (CsmRegs.CSMSCR.bit.SECURE) return 0x1004U; /* Не начинает стирание защищённого CSM устройства и передаёт клиенту стандартный статус защиты. */
|
||||
memcpy(&FlashApiRunStart, &FlashApiLoadStart, &FlashApiLoadEnd - &FlashApiLoadStart); /* Все .text и .econst TI API копируются во внутреннюю RAM. */
|
||||
Flash_CPUScaleFactor = (Uint32)(1048576.0L * 200.0L * F28335_RS_CPU_HZ / 1000000000.0L); /* Формула IQ20 из TI v2.10 учитывает фактическую частоту CPU, единицы — Гц. */
|
||||
Flash_CallbackPtr = 0; ops.erase = erase; ops.program = program; ops.read = F28335Rs_ReadFlash; ops.user = 0; /* Callback во Flash во время erase/program запрещён. */
|
||||
api_error = 0U; if (f28335_flash_update_program(image, &ops)) return 0U; /* Повторная CRC staging, запись FAB1 последним и CRC физической Flash находятся в общем writer; ноль означает подтверждённый успех. */
|
||||
return api_error ? api_error : 0x1006U; /* Ошибка TI сохраняет свой код, отказ структуры/readback без ошибки API получает отдельный ненулевой статус. */
|
||||
} /* После отказа управление остаётся в постоянном загрузчике и допускает новый LOAD. */
|
||||
@@ -0,0 +1,79 @@
|
||||
#include "../include/f28335_rs_boot.h" /* Единственная реализация автомата используется платой и host-тестами. */
|
||||
#include <string.h> /* Сбрасывает состояние без динамического выделения памяти. */
|
||||
static unsigned int crc16(unsigned int crc, unsigned int value) /* CRC16 Modbus защищает запросы и ответы существующего periph_28335. */
|
||||
{ /* Все операции явно ограничены 16 битами и подходят для 16-битного int C28x. */
|
||||
unsigned int bit; /* Каждый принятый октет содержит ровно восемь значащих битов. */
|
||||
crc ^= value & 255U; /* Старшие биты аппаратного регистра SCI не являются данными протокола. */
|
||||
for (bit = 0U; bit < 8U; ++bit) crc = (crc & 1U) ? ((crc >> 1) ^ 0xA001U) : (crc >> 1); /* Отражённый полином A001 соответствует CRC16 Modbus. */
|
||||
return crc & 65535U; /* На ПК unsigned int шире C28x, поэтому маска сохраняет одинаковый результат. */
|
||||
} /* Завершает переносимый шаг CRC. */
|
||||
static uint32_t get32(const unsigned int *p) /* Поля BIOS передаются младшим октетом вперёд, в отличие от адреса внутри SCI8. */
|
||||
{ /* Приведение перед сдвигом исключает потерю старших битов на 16-битном int. */
|
||||
return (uint32_t)p[0] | ((uint32_t)p[1] << 8) | ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24); /* Возвращает адрес слова либо длину октетов по назначению поля. */
|
||||
} /* Формат запроса совпадает с periph28335_firmware.request. */
|
||||
static bool reply(f28335_rs_boot_t *s, unsigned int command, unsigned int status) /* TFLASH сообщает конкретный статус, остальные успешные команды — короткий ACK. */
|
||||
{ /* Ответ не содержит хвостового паддинга, который клиент periph_28335 считает необязательным. */
|
||||
unsigned int frame[6], crc = 0xFFFFU, i, size = command == 55U ? 4U : 2U; /* CMD_TFLASH=55 включает два октета результата; ACK INITLOAD/LOAD содержит адрес и команду. */
|
||||
frame[0] = s->address; frame[1] = command; frame[2] = status & 255U; frame[3] = status >> 8; /* Сохраняет принятый адрес и little-endian статус. */
|
||||
for (i = 0U; i < size; ++i) crc = crc16(crc, frame[i]); /* Контрольная сумма защищает все значащие поля ответа. */
|
||||
frame[size] = crc & 255U; frame[size + 1U] = crc >> 8; /* CRC16 передаётся младшим октетом вперёд. */
|
||||
return s->port.transmit(s->port.user, frame, size + 2U); /* Reset разрешается только после фактического завершения передачи. */
|
||||
} /* Ошибка UART никогда не превращается в разрешение перезапуска. */
|
||||
void f28335_rs_abort(f28335_rs_boot_t *s) /* Инвалидирует частичный кадр и прежнее подтверждение образа. */
|
||||
{ /* Порт вызывает эту функцию при межбайтовом таймауте, переполнении либо RXERROR. */
|
||||
s->position = 0UL; s->loaded = 0U; s->length = 0UL; s->image.valid = false; /* Новый TFLASH после сбоя требует полного нового INITLOAD/LOAD. */
|
||||
} /* Прерывание сеанса не изменяет действующую Flash. */
|
||||
void f28335_rs_init(f28335_rs_boot_t *s, const f28335_rs_port_t *port, unsigned int address) /* Создаёт независимый экземпляр без скрытых глобальных переменных. */
|
||||
{ /* Конфигурация платы должна передать все обязательные callbacks. */
|
||||
memset(s, 0, sizeof(*s)); s->port = *port; s->address = address; /* Обнуляет кеш, счётчики и разрешение LOAD до первого принятого кадра. */
|
||||
} /* Аппаратная инициализация SCI и GPIO остаётся в проекте платы. */
|
||||
static void execute(f28335_rs_boot_t *s) /* Выполняет только полностью принятый запрос с подтверждённой CRC16. */
|
||||
{ /* Транспортные поля никогда не разыменовываются как произвольные CPU-адреса. */
|
||||
uint32_t source, destination, length; unsigned int status = 0U; pcan_boot_manifest_t manifest; /* Manifest общего валидатора используется только для размера и транспортной CRC32. */
|
||||
if (s->command == 58U) { /* CMD_INITLOAD=58 резервирует фиксированный безопасный staging вместо произвольной записи памяти. */
|
||||
source = get32(s->header + 2); length = get32(s->header + 6); s->loaded = 0U; s->length = 0UL; /* Новая подготовка всегда отзывает подтверждение прежнего блока. */
|
||||
if (source == F28335_RS_STAGE && length >= 24UL && length <= F28335_RS_LIMIT && !(length & 1UL)) { s->length = length; reply(s, 58U, 0U); } /* Чётный SCI8 должен поместиться в предел общего проверяемого формата. */
|
||||
} else if (s->command == 51U) { /* CMD_LOAD=51 уже принял поток в staging, но до этой точки не разрешал запись Flash. */
|
||||
if (!s->fault && s->length) { s->loaded = 1U; reply(s, 51U, 0U); } /* Подтверждает блок только после полного приёма, успешной записи RAM и CRC16. */
|
||||
} else if (s->command == 55U) { /* CMD_TFLASH=55 совместим с шестибайтовым статусом periph_28335. */
|
||||
destination = get32(s->header + 2); source = get32(s->header + 6); length = get32(s->header + 10); /* Назначение, источник и размер сохраняют порядок полей существующего GUI. */
|
||||
if (destination || source || length) { /* Нулевая тройка служит безопасной пробой поддержки без стирания и запуска. */
|
||||
if (!s->loaded || destination != F28335_FLASH_APP_START || source != F28335_RS_STAGE || length != s->length) status = 0x1003U; /* Требует ровно тот блок, который подтвердил завершённый LOAD. */
|
||||
else { /* Общий валидатор повторно читает RAM и сравнивает CRC32, записанную при приёме. */
|
||||
memset(&manifest, 0, sizeof(manifest)); manifest.image_size = length; manifest.image_crc32 = s->crc32; /* Не доверяет CRC из непроверенного пользовательского manifest. */
|
||||
if (!f28335_flash_update_validate(&s->image, &s->port.stage, 0UL, &manifest)) status = 0x1002U; /* Отвергает сектор A, CSM, OTP, перекрытия, неверный FAB1 и повреждённые данные до erase. */
|
||||
else status = s->port.program(s->port.user, &s->image); /* Сохраняет конкретный код TI, защиту CSM 1004 и ошибку readback 1006; отказ оставляет RS recovery доступным. */
|
||||
} /* Завершает проверку и аппаратную запись текущего образа. */
|
||||
s->loaded = 0U; /* Повторная команда записи требует нового подтверждённого LOAD независимо от результата. */
|
||||
} /* Безопасная проба не потребляет подтверждённый блок RAM. */
|
||||
if (reply(s, 55U, status) && !status && length) s->port.reboot(s->port.user); /* Успешный reset следует только после записи, проверки физической Flash и отправки ACK. */
|
||||
} /* Остальные команды не поддерживаются и не меняют память. */
|
||||
} /* EEPROM параметров не входит ни в один callback загрузчика. */
|
||||
void f28335_rs_feed(f28335_rs_boot_t *s, unsigned int value) /* Разбирает запросы последовательно без большого приёмного массива. */
|
||||
{ /* Один вызов обрабатывает ровно один октет; время межбайтового ожидания контролирует порт. */
|
||||
uint32_t position = s->position; pcan_boot_byte_t octet = value & 255U; /* Маска исключает флаги SCI и старшие биты широкого char. */
|
||||
if (!position) { /* Новый кадр начинается только с адреса этого устройства. */
|
||||
if (octet != s->address || !s->address || s->address > 255U) return; /* Чужой адрес не получает ACK и не запускает запись RAM. */
|
||||
s->crc16 = 0xFFFFU; s->wire_crc = 0U; s->fault = 0U; /* CRC16 каждого запроса начинается со значения FFFF протокола Modbus. */
|
||||
} /* После первого октета автомат ожидает номер команды. */
|
||||
if (position == 1UL) { /* Второй октет задаёт фиксированную либо потоковую длину кадра. */
|
||||
s->command = octet; /* Сохраняет неизменный номер BIOS-команды. */
|
||||
if (octet == 58U) s->expected = 12UL; /* INITLOAD содержит адрес/команду, два u32 и CRC16: 12 октетов. */
|
||||
else if (octet == 55U) s->expected = 16UL; /* TFLASH содержит три u32 и занимает 16 октетов с CRC16. */
|
||||
else if (octet == 51U && s->length) { s->expected = s->length + 4UL; s->loaded = 0U; s->crc32 = 0UL; } /* LOAD состоит из адреса/команды, точного SCI8 и CRC16. */
|
||||
else { f28335_rs_abort(s); return; } /* Неизвестная команда не позволяет использовать оставшиеся байты как адрес CPU. */
|
||||
} /* Только распознанный заголовок разрешает приём полезных данных. */
|
||||
if (position < 2UL || s->command != 51U) { if (position < 16UL) s->header[position] = octet; } /* Фиксированные запросы сохраняются в ограниченном заголовке. */
|
||||
else if (position < s->expected - 2UL) { /* Поток LOAD записывается в RAM, пока завершающая CRC16 ещё не подтверждена. */
|
||||
if (!s->port.stage_write(s->port.user, position - 2UL, octet)) s->fault = 1U; /* Ошибка RAM запрещает подтверждение и последующий erase. */
|
||||
s->crc32 = pcan_boot_crc32_update(s->crc32, &octet, 1U); /* Независимая CRC32 позволяет обнаружить изменение staging после LOAD. */
|
||||
} /* Два завершающих октета CRC не попадают в staging образа. */
|
||||
if (position < 2UL || position < s->expected - 2UL) s->crc16 = crc16(s->crc16, octet); /* CRC16 включает адрес, команду и всю полезную нагрузку. */
|
||||
else if (position == s->expected - 2UL) s->wire_crc = octet; /* Первый октет контрольной суммы является младшим. */
|
||||
else s->wire_crc |= (unsigned int)octet << 8; /* Второй октет завершает 16-битную CRC принятого кадра. */
|
||||
s->position = position + 1UL; /* Продвигает курсор только после обработки текущего октета. */
|
||||
if (position >= 1UL && s->position == s->expected) { /* Запрос исполняется ровно один раз после получения последнего октета. */
|
||||
s->position = 0UL; /* Следующий адрес начнёт независимый кадр даже при ошибочной CRC текущего. */
|
||||
if (s->wire_crc == s->crc16) execute(s); else f28335_rs_abort(s); /* Повреждённый кадр отзывает прежний LOAD и никогда не вызывает program. */
|
||||
} /* Частичный кадр остаётся в ожидании следующего октета либо внешнего timeout. */
|
||||
} /* Автомат не использует системные часы, регистры MCU или динамическую память. */
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,88 @@
|
||||
"""Проверяет production-автомат RS и общий writer на модели сбоев без платы.""" # Не вызывает JTAG, COM и TI Flash API.
|
||||
import ctypes # Передаёт октеты настоящему скомпилированному C-автомату.
|
||||
import importlib.util # Использует общий подписыватель FAB1 для корректных тестовых образов.
|
||||
import os # Добавляет runtime GCC только в окружение тестового процесса.
|
||||
from pathlib import Path # Разрешает пути относительно установленной библиотеки templates.
|
||||
import struct # Формирует стандартные поля запросов и адресные секции SCI8.
|
||||
import subprocess # Собирает C-модель с production-исходниками GCC.
|
||||
import tempfile # Удаляет только собственные временные DLL проверки.
|
||||
import unittest # Каждый отказ выполняется независимо и получает отдельный результат.
|
||||
LIBRARY = Path(__file__).resolve().parents[1]; TEMPLATES = LIBRARY.parents[1]; PORT = TEMPLATES / "c/protocan-boot/ports/tms320f28335" # Тест использует те же валидатор и writer, которые компилируются для платы.
|
||||
spec = importlib.util.spec_from_file_location("boot_test_contract", TEMPLATES / "python/protocan/f28335_flash.py"); contract = importlib.util.module_from_spec(spec); spec.loader.exec_module(contract) # Эталон FAB1 берётся из действующей общей библиотеки.
|
||||
|
||||
def section(address, words): # Создаёт адресный блок TI без дополнительных правил обновления.
|
||||
return struct.pack("<HHH", len(words), address >> 16, address & 65535) + struct.pack("<" + "H" * len(words), *words) # Длина считается словами, каждое слово little endian, адрес — старшим словом вперёд.
|
||||
|
||||
def image(): # Минимальный правильный образ содержит разрыв для проверки CRC стёртых промежутков.
|
||||
entry = contract.APP_DATA; loaded = {entry: 0x1234, entry + 1: 0x5678, entry + 12: 0xAABB, entry + 13: 0xCCDD} # Промежуток между двумя секциями должен остаться FFFF после erase.
|
||||
return b"\xaa\x08" + bytes(16) + struct.pack("<HH", entry >> 16, entry & 65535) + section(contract.APP_START, struct.unpack("<16H", contract.descriptor(entry, loaded))) + section(entry, [0x1234, 0x5678]) + section(entry + 12, [0xAABB, 0xCCDD]) + bytes(2) # Заголовок FAB1 передаётся первым, но writer обязан опубликовать его последним.
|
||||
|
||||
class BootTests(unittest.TestCase): # Проверяет наблюдаемое поведение полного обмена, а не отдельные строки реализации.
|
||||
@classmethod # DLL компилируется один раз для всех сценариев отказа.
|
||||
def setUpClass(cls): # Изолирует бинарный результат и зависимости GCC.
|
||||
gcc = Path(os.environ.get("CC", "C:/msys64/mingw64/bin/gcc.exe")); os.environ["PATH"] = str(gcc.parent) + os.pathsep + os.environ["PATH"] # CC позволяет выбрать другой совместимый GCC без изменения теста.
|
||||
cls.temporary = tempfile.TemporaryDirectory(prefix="f28335-rs-tests-"); dll = Path(cls.temporary.name) / "model.dll" # Все артефакты испытания находятся вне прошивки пользователя.
|
||||
subprocess.run([str(gcc), "-shared", "-std=c99", "-Wall", "-Wextra", "-Werror", str(LIBRARY / "tests/model.c"), str(PORT / "f28335_flash_app.c"), str(PORT / "f28335_flash_update.c"), str(TEMPLATES / "c/protocan-boot/src/pcan_boot.c"), "-o", str(dll)], check=True) # Компилирует production parser, CRC, validator и writer без извлечения копий функций.
|
||||
cls.native = ctypes.CDLL(str(dll)); cls.native.model_crc.argtypes = [ctypes.c_char_p, ctypes.c_uint]; cls.native.model_crc.restype = ctypes.c_uint; cls.native.model_value.restype = ctypes.c_uint # ABI содержит только явно заданные целые числа и октеты.
|
||||
|
||||
@classmethod # Windows требует освободить библиотеку до удаления DLL.
|
||||
def tearDownClass(cls): # Закрывает только дескриптор, созданный этим тестом.
|
||||
if os.name == "nt": # На Windows открытая DLL удерживает файл от удаления.
|
||||
release = ctypes.WinDLL("kernel32").FreeLibrary; release.argtypes = [ctypes.c_void_p]; release.restype = ctypes.c_int; release(cls.native._handle) # Освобождает единственную тестовую DLL с полным размером указателя.
|
||||
cls.temporary.cleanup() # Временные файлы не остаются в библиотеке или проекте ИБП.
|
||||
|
||||
def setUp(self): self.native.model_reset() # Стирает модель между тестами, сохраняя независимость сценариев.
|
||||
|
||||
def frame(self, command, payload=b"", address=16): # Формирует запросы совместимого BIOS для управления production-автоматом.
|
||||
body = bytes((address, command)) + payload; return body + struct.pack("<H", self.native.model_crc(body, len(body))) # Повреждение CRC тестируется отдельно, а фиксированный probe-вектор проверяет численное значение.
|
||||
|
||||
def send(self, data): # Аппаратные границы чтений могут разделить кадр после любого октета.
|
||||
for octet in data: self.native.model_feed(octet) # Никакая модель не собирает кадр за production-автомат.
|
||||
return bytes(self.native.model_value(10 + i) for i in range(self.native.model_value(0))) # Получает фактический ACK/status, отправленный callback transmit.
|
||||
|
||||
def load(self, payload): # Выполняет стандартный INITLOAD/LOAD без TFLASH.
|
||||
self.assertEqual(len(self.send(self.frame(58, struct.pack("<II", 0x200000, len(payload))))), 4) # Подготовка RAM требует точного короткого ACK.
|
||||
self.assertEqual(len(self.send(self.frame(51, payload))), 4) # Правильный LOAD подтверждается только после полной CRC16.
|
||||
|
||||
def program(self, payload): return self.send(self.frame(55, struct.pack("<III", 0x300000, 0x200000, len(payload)))) # Адреса и порядок полей совпадают с program_request periph_28335.
|
||||
|
||||
def assert_protected(self): self.assertEqual(self.native.model_value(5), 0xA55A) # Постоянный сектор A должен сохраняться даже после отказа на любой стадии записи.
|
||||
|
||||
def test_probe_and_success(self): # Полный успешный обмен должен подтвердить физическую Flash до reset.
|
||||
probe = self.frame(55, bytes(12)); self.assertEqual(probe.hex(), "1037000000000000000000000000f066") # Вектор независимо подтверждён setprotocol.dll через protocan.periph28335.with_crc и обнаруживает изменение wire-формата.
|
||||
response = self.send(probe); self.assertEqual(response[:4], bytes((16, 55, 0, 0))); self.assertEqual(self.native.model_value(1), 0) # Probe ничего не стирает и не останавливает действующую программу.
|
||||
payload = image(); self.load(payload); response = self.program(payload); self.assertEqual(response[:4], bytes((16, 55, 0, 0))) # Успешный TFLASH сообщает нулевой статус.
|
||||
self.assertEqual(self.native.model_value(4), 1); self.assertEqual(self.native.model_value(3), 1); self.assert_protected() # Реальный валидатор принимает физическую Flash, и только затем запрошен reset.
|
||||
|
||||
def test_wrong_address_crc_and_timeout(self): # Чужие либо оборванные запросы не должны разрешать запись.
|
||||
payload = image(); self.assertEqual(self.send(self.frame(58, struct.pack("<II", 0x200000, len(payload)), address=17)), b"") # Чужой адрес не получает подтверждения INITLOAD.
|
||||
self.load(payload); self.send(self.frame(58, struct.pack("<II", 0x200000, len(payload)))[:5]); self.native.model_abort() # Таймаут отбрасывает недопринятый запрос и старое подтверждение LOAD.
|
||||
self.assertEqual(self.program(payload)[2:4], b"\x03\x10"); self.assertEqual(self.native.model_value(1), 0) # Без нового LOAD статус 1003 возникает до erase.
|
||||
self.load(payload); damaged = bytearray(self.frame(55, struct.pack("<III", 0x300000, 0x200000, len(payload)))); damaged[-1] ^= 1; self.assertEqual(self.send(damaged), b"") # Неверная CRC TFLASH не вызывает никакого ответа об успехе.
|
||||
self.assertEqual(self.native.model_value(1), 0); self.assert_protected() # Ни timeout, ни повреждение запроса не затронули Flash.
|
||||
|
||||
def test_changed_ram_and_read_failure(self): # CRC32 staging повторно проверяется после успешного LOAD.
|
||||
payload = image(); self.load(payload); self.native.model_corrupt(60); self.assertEqual(self.program(payload)[2:4], b"\x02\x10") # Изменение RAM не скрывается первоначальным успешным ACK LOAD.
|
||||
self.assertEqual(self.native.model_value(1), 0); self.load(payload); self.native.model_fault(0, 0, 1); self.assertEqual(self.program(payload)[2:4], b"\x02\x10") # Ошибка чтения staging также запрещает первый erase.
|
||||
self.assertEqual(self.native.model_value(1), 0); self.assert_protected() # Память приложения и boot остались прежними.
|
||||
|
||||
def test_forbidden_addresses_and_bad_image(self): # Проверяет все опасные границы Flash и неполную таблицу.
|
||||
for address in (0x2FFFFF, 0x338000, 0x33FFF6, 0x33FFF8, 0x380400): # Включает RAM/границу приложения, boot, CSM и OTP.
|
||||
with self.subTest(address=hex(address)): # Ошибка конкретного диапазона видна отдельно в отчёте.
|
||||
self.native.model_reset(); payload = image()[:-2] + section(address, [1, 2]) + bytes(2); self.load(payload); self.assertEqual(self.program(payload)[2:4], b"\x02\x10") # Правильная CRC16 не разрешает опасный LOAD-адрес внутри SCI8.
|
||||
self.assertEqual(self.native.model_value(1), 0); self.assert_protected() # Неверная структура отвергается раньше физического стирания.
|
||||
for payload in (image()[:-2], image() + bytes(2), image()[:-2] + section(contract.APP_DATA, [1]) + bytes(2)): # Проверяет отсутствующий терминатор, хвост и перекрытие секций.
|
||||
self.native.model_reset(); self.load(payload); self.assertEqual(self.program(payload)[2:4], b"\x02\x10"); self.assertEqual(self.native.model_value(1), 0) # Любое нарушение структуры запрещает erase.
|
||||
|
||||
def test_failure_at_each_write_and_retry(self): # Имитирует обрыв каждого этапа программирования и последующее восстановление.
|
||||
for operation in (100, 1, 2, 3): # 100 обозначает erase; три program-порции — две секции данных и последний FAB1.
|
||||
with self.subTest(operation=operation): # Все стадии проверяются независимо на свежей модели Flash.
|
||||
self.native.model_reset(); payload = image(); self.load(payload); self.native.model_fault(operation, 0, 0); self.assertEqual(self.program(payload)[2:4], b"\x16\x00") # Отказ writer передаёт статус ошибки TI вместо успешного ACK.
|
||||
self.assertEqual(self.native.model_value(4), 0); self.assertEqual(self.native.model_value(3), 0); self.assert_protected() # Частичный образ не запускается, reset не запрошен, постоянный boot сохранён.
|
||||
self.native.model_fault(0, 0, 0); self.load(payload); self.assertEqual(self.program(payload)[2:4], bytes(2)); self.assertEqual(self.native.model_value(4), 1) # Новый полный LOAD восстанавливает приложение через тот же оставшийся boot.
|
||||
|
||||
def test_failed_ack_prevents_reset(self): # Потеря передачи результата не должна перезапускать контроллер неожиданно для клиента.
|
||||
payload = image(); self.load(payload); self.native.model_fault(0, 1, 0); self.program(payload) # Запись проходит успешно, но transmit возвращает ошибку.
|
||||
self.assertEqual(self.native.model_value(4), 1); self.assertEqual(self.native.model_value(3), 0); self.assert_protected() # Физическая Flash готова, однако boot остаётся в recovery до явного последующего reset.
|
||||
|
||||
if __name__ == "__main__": unittest.main(verbosity=2) # Самостоятельный запуск выводит результаты модели без утверждений о проверке платы.
|
||||
@@ -0,0 +1,105 @@
|
||||
"""Собирает постоянный RS boot и выпускает проверенное Flash-приложение CCS.""" # Все аппаратные пути и исходники передаются проектом платы.
|
||||
import argparse # Разбирает режим полной сборки либо postbuild уже собранного приложения.
|
||||
from datetime import datetime # Восстанавливает двенадцать слов встроенного паспорта текущего prebuild.
|
||||
import importlib.util # Подключает существующие инструменты firmware-info и FAB1 без копирования алгоритмов.
|
||||
import json # Читает проектные настройки и сохраняет отдельные manifest приложения и boot.
|
||||
from pathlib import Path # Разрешает пути независимо от текущего каталога CCS.
|
||||
import re # Разбирает стандартные относительные ссылки PARENT-N-PROJECT_LOC в Eclipse.
|
||||
import shutil # Переносит только проверенный комплект из временного каталога.
|
||||
import struct # Сопоставляет двенадцать 16-битных слов паспорта точным октетам COFF.
|
||||
import subprocess # Запускает TI compiler/OFD/hex2000 без shell и без доступа к программатору.
|
||||
import tempfile # Изолирует объекты и неполные выпуски текущей попытки.
|
||||
import xml.etree.ElementTree as ET # Читает реальные linked resources и include paths выбранной конфигурации CCS.
|
||||
TEMPLATES = Path(__file__).resolve().parents[3] # Каталог tools расположен в templates/c/f28335-rs-boot/tools.
|
||||
LIBRARY = Path(__file__).resolve().parents[1] # Общие исходники и linker boot берутся из того же checkout templates.
|
||||
FLASH = TEMPLATES / "c/protocan-boot/ports/tms320f28335" # Использует единые валидатор FAB1 и writer SCI8.
|
||||
|
||||
def module(name, path): # Загружает существующий инструмент по конкретному пути выбранной библиотеки.
|
||||
spec = importlib.util.spec_from_file_location(name, path) # Избегает случайного импорта другого установленного protocan.
|
||||
result = importlib.util.module_from_spec(spec); spec.loader.exec_module(result); return result # Ошибка общей зависимости останавливает выпуск.
|
||||
|
||||
identity = module("rs_boot_identity", TEMPLATES / "c/firmware-info/tools/identity.py") # Сохраняет общие manifest, SHA-256 и имена архивов.
|
||||
contract = module("rs_boot_contract", TEMPLATES / "python/protocan/f28335_flash.py") # Сохраняет единый формат подписания и проверки FAB1.
|
||||
|
||||
def project_paths(args): # Получает фактические C/ASM и include paths из настроек проекта.
|
||||
project = args.project.resolve(); sources = [] # Проектный каталог содержит .project/.cproject и generated отдельной конфигурации.
|
||||
for link in ET.parse(project / ".project").findall(".//linkedResources/link"): # Заголовки, OBJ и старые linker-файлы не компилируются как исходники.
|
||||
uri = link.findtext("locationURI", ""); prefix, separator, relative = uri.partition("/") # Eclipse хранит корень ссылки отдельно от относительного пути.
|
||||
match = re.fullmatch(r"PARENT-(\d+)-PROJECT_LOC", prefix) # Поддерживает переносимые ссылки любого уровня, не зашивая репозиторий платы.
|
||||
root = project.parents[int(match[1]) - 1] if match else project # PARENT-1 указывает непосредственно на родителя CCS-проекта.
|
||||
if separator and (match or prefix == "PROJECT_LOC") and Path(relative).suffix.lower() in (".c", ".asm"): sources.append((root / relative).resolve()) # Сохраняет только реальные C/ASM linked resources.
|
||||
sources.extend((project / "setgui_firmware_identity").glob("*.c")) # Подключённый ранее паспорт SETGUI остаётся частью приложения.
|
||||
config = next(item for item in ET.parse(project / ".cproject").findall(".//configuration") if item.get("name") == args.configuration) # Выбирает именно Debug либо Release, запрошенный CCS.
|
||||
option = next(item for item in config.findall(".//tool/option") if "compilerID.INCLUDE_PATH" in item.get("superClass", "")) # Сохраняет порядок поиска действующих заголовков платы.
|
||||
includes = []; macros = {"${PROJECT_ROOT}": project, "${PROJECT_LOC}": project, "${CG_TOOL_ROOT}": args.compiler.resolve(), "${BuildDirectory}": args.build_dir.resolve()} # Разрешает только ожидаемые файловые макросы CCS.
|
||||
for item in option.findall("listOptionValue"): # Каждая include-настройка применяется без shell expansion.
|
||||
value = item.get("value", "") # Пустой путь не должен означать случайный текущий каталог.
|
||||
for macro, replacement in macros.items(): value = value.replace(macro, str(replacement)) # Подставляет фактические абсолютные пути текущей конфигурации.
|
||||
if not value or "${" in value: raise ValueError("Не разрешён include path CCS: " + value) # Отсутствующую зависимость требуется исправить до компиляции.
|
||||
includes.append(Path(value)) # Сохраняет приоритет dev_sup перед дополнительными TI-заголовками.
|
||||
return list(dict.fromkeys(sources)), includes # Не компилирует повторную ссылку на один исходник дважды.
|
||||
|
||||
def compile_image(args, sources, includes, linker, output, optimize=False, api=False): # Создаёт самостоятельный COFF с собственным runtime.
|
||||
compiler = args.compiler.resolve(); support = args.support.resolve(); objects = [] # Объекты boot не смешиваются с объектами приложения.
|
||||
with tempfile.TemporaryDirectory(prefix="objects-", dir=output.parent) as directory: # Не использует старые OBJ из Debug либо другой версии исходников.
|
||||
for index, source in enumerate(sources): # Уникальный индекс исключает коллизии одинаковых имён модулей из разных каталогов.
|
||||
obj = Path(directory) / (str(index) + "_" + source.stem + ".obj") # Каждый результат принадлежит только этой попытке сборки.
|
||||
command = [str(compiler / "bin/cl2000.exe"), "-v28", "-ml", "-mt", "--float_support=fpu32", "--abi=coffabi", "--gen_func_subsections=on", "--define=F28335_RS_CPU_HZ=150000000UL"] # Архитектура и частота соответствуют F28335 платы ИБП; частота используется формулой TI Flash API.
|
||||
if source.suffix.lower() == ".c": command.append("--c99") # C99 требуется общим bool/stdint, но не передаётся как режим ассемблера.
|
||||
if optimize: command.append("-O2") # Оптимизация boot уменьшает постоянный сектор A; алгоритмы управления приложения сохраняют штатный режим без оптимизации.
|
||||
command.extend("--include_path=" + str(path) for path in includes) # Пути передаются отдельными аргументами, включая пробелы Windows.
|
||||
subprocess.run(command + ["--compile_only", "--output_file=" + str(obj), str(source)], check=True); objects.append(obj) # Ненулевой код TI останавливает сборку до линковки.
|
||||
command = [str(compiler / "bin/cl2000.exe"), "-v28", "--float_support=fpu32", "--abi=coffabi", "-z", "--rom_model", "--heap_size=0", "--stack_size=0x400", "--warn_sections", "--search_path=" + str(compiler / "lib"), "--search_path=" + str(support / "source")] # Отдельный runtime сохраняет boot работоспособным при пустом приложении.
|
||||
if api: command.append("--search_path=" + str(args.flash_api.resolve() / "lib")) # Библиотека TI берётся из локальной установки и не копируется в templates.
|
||||
command.extend(str(path) for path in objects) # Линкер получает только свежие объекты этой попытки.
|
||||
command.extend([str(linker), str(support / "source/DSP2833x_Headers_nonBIOS.cmd"), "-lrts2800_fpu32.lib", "--map_file=" + str(output.with_suffix(".map")), "--output_file=" + str(output)]) # Общие регистры и runtime согласованы с COFF/FPU32 действующего проекта.
|
||||
if not api: command.append(str(support / "source/rts2800_fpu32_fast_supplement.lib")) # Приложение сохраняет ускоренные sincos/sqrt/div/atan2 в прежней ramfuncs.
|
||||
subprocess.run(command, check=True) # Отсутствие Flash API или переполнение RAM/Flash запрещает выпуск.
|
||||
|
||||
def build_boot(args, output): # Компилирует постоянный сектор A с независимыми исходниками платы.
|
||||
support = args.support.resolve(); port = LIBRARY / "ports/tms320f28335" # Общий аппаратный порт не содержит GPIO конкретного изделия.
|
||||
sources = [args.board.resolve(), LIBRARY / "src/f28335_rs_boot.c", port / "flash_api.c", FLASH / "f28335_flash_app.c", FLASH / "f28335_flash_update.c", TEMPLATES / "c/protocan-boot/src/pcan_boot.c", support / "source/DSP2833x_GlobalVariableDefs.c", support / "source/init28335_func.c", support / "source/DSP2833x_CodeStartBranch.asm"] # UART/GPIO принадлежат только board.c; формат и writer повторно используются из templates.
|
||||
includes = [support / "include", LIBRARY / "include", args.flash_api.resolve() / "include", args.compiler.resolve() / "include"] # Все зависимости TI должны существовать до компиляции.
|
||||
compile_image(args, sources, includes, port / "boot.cmd", output, optimize=True, api=True) # Boot использует только FLASHA и выделенные RAMAPI/RAMFUNCS.
|
||||
document = ET.fromstring(subprocess.check_output([str(args.compiler / "bin/ofd2000.exe"), "-x", str(output)])) # Проверяет фактические LOAD-секции, а не только текст linker-команды.
|
||||
for section in document.findall(".//ti_coff/section"): # Учитывает также Flash LOAD копируемого в RAM кода API.
|
||||
if section.findtext("raw_data") is None or section.findtext("copy") == "true": continue # Незагружаемые секции отладки не программируются.
|
||||
start = int(section.findtext("virtual_addr", "0"), 0); size = int(section.findtext("raw_data_size", "0"), 0) // 2 # OFD хранит LOAD как virtual_addr, размер raw_data измеряет октетами.
|
||||
if not (0x338000 <= start < start + size <= 0x33FF80 or start == 0x33FFF6 and size == 2): raise ValueError("Boot OUT содержит LOAD вне постоянного сектора A") # Запрещает приложение, RAM LOAD, CSM и OTP в установочном образе boot.
|
||||
|
||||
def publish(args): # Проверяет оба независимых OUT перед переносом нового выпуска в постоянный каталог.
|
||||
project = args.project.resolve(); build = args.build_dir.resolve(); metadata_path = build / "generated/firmware_identity.json" # Каждый Debug/Release использует собственный паспорт prebuild.
|
||||
metadata = json.loads(metadata_path.read_text(encoding="utf-8")); settings = json.loads((project / "firmware_identity.json").read_text(encoding="utf-8-sig")) # Не резервирует дополнительную версию после компиляции.
|
||||
if any(metadata.get(field) != settings.get(field) for field in ("product", "target", "version", "flashing")): raise ValueError("Настройки изменились: повторите prebuild и сборку") # Не связывает старый OUT с новым продуктом, номером либо маршрутом прошивки.
|
||||
output = build / "firmware"; output.mkdir(parents=True, exist_ok=True) # Все временные и постоянные результаты принадлежат выбранной конфигурации CCS.
|
||||
with tempfile.TemporaryDirectory(prefix=".rs-release-", dir=build) as directory: # Частичная сборка никогда не заменяет предыдущий выпуск.
|
||||
stage = Path(directory); app = stage / "flash" / (args.artifact + ".out"); app.parent.mkdir() # Flash OUT приложения хранится отдельно от boot.
|
||||
shutil.copy2(build / (args.artifact + ".out"), app); contract.seal_coff(app, args.compiler / "bin/ofd2000.exe") # Заполняет только заранее выделенный FAB1 после проверки всех LOAD-адресов приложения.
|
||||
document = ET.fromstring(subprocess.check_output([str(args.compiler / "bin/ofd2000.exe"), "-x", str(app)])); entry, loaded, sections = contract.coff_sections(document) # Содержимое извлекается официальным TI OFD из реального COFF.
|
||||
built = datetime.fromisoformat(metadata["built_at_utc"]); build_id = metadata["build_id"].encode("ascii"); expected = (1, *metadata["version"], built.year, built.month << 8 | built.day, built.hour << 8 | built.minute, built.second, *(build_id[i] << 8 | build_id[i + 1] for i in range(0, 8, 2))) # Двенадцать слов повторяют публичный firmware_info_static контракт паспорта без изменения версии.
|
||||
if sum(tuple(loaded.get(address + i) for i in range(12)) == expected for address in loaded) != 1: raise ValueError("OUT не содержит единственный паспорт текущего prebuild") # Старый объект, чужая версия либо дублированный паспорт не выпускаются.
|
||||
serial = stage / "rs" / (args.artifact + "_flash_rs.bin"); serial.parent.mkdir() # RS-клиент выбирает только SCI8 приложения, исключая постоянный boot.
|
||||
subprocess.run([str(args.compiler / "bin/hex2000.exe"), str(app), "-boot", "-sci8", "-b", "-o", str(serial), "-map", str(serial.with_suffix(".bin.map"))], check=True) # Официальный конвертор сохраняет адресные секции TI SCI8.
|
||||
contract.parse_sci8(serial.read_bytes()); boot = stage / "boot" / (args.artifact + "_boot.out"); boot.parent.mkdir(); build_boot(args, boot) # Проверяет BIN независимо от OUT и создаёт отдельный проверенный установочный boot.
|
||||
shutil.copy2(build / (args.artifact + ".map"), app.with_suffix(".map")) # Карта соответствует неизменным адресам приложения до заполнения заголовка.
|
||||
for image, kind in ((app, "COFF-FLASH"), (serial, "SCI8-FLASH"), (boot, "COFF-RS-BOOT")): # Каждый тип получает собственный manifest и SHA-256 точных байтов.
|
||||
specific = dict(metadata, execution="BOOTLOADER" if image == boot else "FLASH", image_format=kind, boot_contract="f28335-universal-v1", boot_protected=True) # Явно разделяет установку постоянного boot и обновление приложения.
|
||||
if image == boot: specific["flashing"] = {"default_loader": "f28335-jtag", "profiles": {"f28335-jtag": {}}} # Постоянный загрузчик устанавливается через JTAG и никогда не выбирается для RS TFLASH.
|
||||
current = stage / "manifest-source.json"; current.write_text(json.dumps(specific, ensure_ascii=False, indent=2), encoding="utf-8"); identity.seal(image, current); identity.verify(image, image.with_suffix(image.suffix + ".manifest.json")) # Все стороны используют существующие проверенные средства firmware-info.
|
||||
(stage / "manifest-source.json").unlink(); shutil.copy2(metadata_path, stage / "firmware_identity.json") # В архив попадает один исходный паспорт текущей компиляции.
|
||||
destination = output / identity.build_folder_name(metadata); shutil.copytree(stage, destination, dirs_exist_ok=True) # Сохраняет предыдущие архивы и переносит только полностью проверенный выпуск.
|
||||
shutil.copy2(app, build / app.name) # Основной OUT CCS получает тот же FAB1, что опубликованные RS BIN и Flash OUT.
|
||||
if settings.get("copy_to") and not args.no_export: identity.export_build(destination, (project / settings["copy_to"]).resolve(), "rs/" + serial.name) # Установленный проектный export проверяет BIN в папке rs и применяется после всех локальных проверок.
|
||||
print("[firmware-build] RS boot package: " + str(destination)); return destination # Показывает точный постоянный каталог для JTAG и последующего RS-обновления.
|
||||
|
||||
def main(): # Одна точка входа используется проектной обёрткой и проверкой воспроизводимости.
|
||||
parser = argparse.ArgumentParser(description=__doc__); parser.add_argument("command", choices=("build", "postbuild")) # build компилирует приложение; postbuild использует OUT, уже собранный CCS.
|
||||
for name in ("project", "compiler", "build-dir", "support", "board", "app-linker", "flash-api"): parser.add_argument("--" + name, type=Path, required=True) # Все аппаратные исходники и установки выбирает проект-потребитель.
|
||||
parser.add_argument("--artifact", required=True); parser.add_argument("--configuration", default="Debug", choices=("Debug", "Release")); parser.add_argument("--no-export", action="store_true") # Локальная проверка может отключить дополнительное копирование в GUI.
|
||||
args = parser.parse_args(); args.compiler = args.compiler.resolve(); args.build_dir = args.build_dir.resolve(); args.build_dir.mkdir(parents=True, exist_ok=True) # Нормализует только ожидаемые пути без shell expansion.
|
||||
if Path(args.artifact).name != args.artifact or any(char in args.artifact for char in '\\/:*?"<>|'): parser.error("artifact должен быть именем OUT без расширения") # Имя файла не может выйти за выбранный каталог выпуска.
|
||||
if args.command == "build": # Ручной полный build полезен для проверки проекта вне запущенной IDE.
|
||||
sources, includes = project_paths(args); compile_image(args, sources, includes, args.app_linker.resolve(), args.build_dir / (args.artifact + ".out")) # Использует действующие linked resources без ручного списка копий исходников.
|
||||
publish(args) # Ненулевые коды компиляции, конверсии и проверки распространяются в CCS.
|
||||
|
||||
if __name__ == "__main__": main() # Импорт тестами не запускает компилятор и не изменяет рабочую версию.
|
||||
File diff suppressed because one or more lines are too long
@@ -15,6 +15,132 @@ User → Stop on Exit Code** для команд **Before Build/Rebuild** и
|
||||
для ручной правки XML сначала закройте проект в Keil и сохраните резервную копию.
|
||||
Обновление templates не меняет уже сохранённые условия в файле проекта.
|
||||
|
||||
## Параметры прошивки CAN и RS из JSON
|
||||
|
||||
Настройка CCS/Keil в SETGUI теперь проверяет Python до изменения проекта.
|
||||
Общие BAT вызывают `python-runtime.ps1`: он находит Python 3.7+ в установленном
|
||||
окружении либо готовит проверенный переносимый runtime в
|
||||
`%LOCALAPPDATA%/SET/FirmwareBuild`. Ручная настройка PATH не требуется.
|
||||
Для Windows 10/11 x64 используется закреплённый Python 3.13.12, для Windows 7
|
||||
и x86 — Python 3.8.10. ZIP загружается с python.org с проверкой SHA-256;
|
||||
повторные сборки используют готовую локальную копию. При отсутствии доступа
|
||||
к сети можно заранее установить Python либо задать полный путь `FIRMWARE_PYTHON`.
|
||||
|
||||
Для **штатного SCI-загрузчика UlitCo / C2Prog** выберите в настройке проекта
|
||||
маршрут `f28335-sci`. Он автоматически подключает общую реализацию из
|
||||
`c/firmware-info/tools/c2000_sci_build.py`; отдельный проектный `build_flash.py`
|
||||
не требуется. Пример блока:
|
||||
|
||||
```json
|
||||
"flashing": {
|
||||
"default_loader": "f28335-sci",
|
||||
"profiles": {
|
||||
"f28335-sci": {
|
||||
"baudrate": 115200,
|
||||
"oscillator_hz": 30000000,
|
||||
"enter_monitor": false,
|
||||
"timeout_ms": 3000
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Укажите фактическую частоту кварца: поддержаны 20, 25 и 30 МГц. Она определяет
|
||||
времена Flash API и сохраняется вместе с RAM-ядром. Установленный C2000Ware с
|
||||
`device_support/f2833x/examples/f28335_flash_kernel` определяется автоматически
|
||||
в `C:/ti` либо по `C2000WARE_ROOT`. Эта зависимость проверяется при применении
|
||||
настройки проекта. TI compiler берётся из `${CG_TOOL_ROOT}` текущей CCS.
|
||||
|
||||
Postbuild проверяет Flash LOAD-секции исходного OUT и выпускает
|
||||
`*_flash_rs.bin`, `*_flash.out`, `f28335_sci_kernel.bin`, manifest, SHA-256
|
||||
и уведомление о лицензии TI. Ядро собирается из штатных исходников SDK под
|
||||
частоту платы и маску секторов именно текущего приложения. Основной manifest
|
||||
в `latest_build.json` указывает сразу на Flash SCI BIN; папка `eeprom` для
|
||||
этого маршрута не создаётся.
|
||||
|
||||
В SETGUI маршрут работает внутри общей вкладки прошивки: первичный загрузчик
|
||||
принимает RAM-ядро TI с побайтовым эхом, затем Kernel A стирает выбранные сектора
|
||||
и принимает порции Flash с подтверждением checksum после Flash API. Внешний
|
||||
C2Prog не запускается. Тайм-аут, несовпадение эха/суммы и отмена завершают
|
||||
операцию; readback-проверка памяти этим протоколом Kernel A не заявляется.
|
||||
Ядро автоматически выбирается из manifest и проверяется по SHA-256.
|
||||
|
||||
`enter_monitor=true` включает штатную команду UlitCo: запись `1` в параметр
|
||||
Monitor №93 перед autobaud. Действующая прошивка разрешает такой переход только
|
||||
в остановленном состоянии. При `false` предварительно переведите плату в
|
||||
первичный SCI-загрузчик обычным способом. Это отдельный маршрут относительно
|
||||
`f28335-flash-rs`, который использует BIOS ПМ35 и защищённое приложение FAB1.
|
||||
|
||||
В проектном `firmware_identity.json` можно хранить блок `flashing` вместе с
|
||||
версией. Keil и CCS проверяют его до увеличения номера сборки и переносят в
|
||||
`generated/firmware_identity.json`, каждый `*.manifest.json` и архив выпуска.
|
||||
Параметры описывают **загрузку образа**; скорость работающей программы на MCU
|
||||
по-прежнему задаётся её исходниками и настройками аппаратуры.
|
||||
|
||||
```json
|
||||
"flashing": {
|
||||
"default_loader": "set-can",
|
||||
"profiles": {
|
||||
"set-can": {"bitrate": 250000, "node_id": 13},
|
||||
"set-rs485": {"baudrate": 115200}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`default_loader` — конкретный загрузчик, выбираемый при импорте сборки или
|
||||
локального образа; его ключ должен присутствовать в `profiles`. Каждая запись
|
||||
`profiles` задаёт независимые параметры соответствующего загрузчика. Значения
|
||||
примеров нужно заменить параметрами своей платы. JSON не допускает комментарии;
|
||||
назначение полей приведено ниже.
|
||||
|
||||
| Загрузчик | Поддерживаемые параметры |
|
||||
|---|---|
|
||||
| `set-can` | `bitrate`, `node_id`, `product_type`, `hardware_min`, `hardware_max`, `base_address` |
|
||||
| `protocan-can` | Поля `set-can`, кроме `base_address`; дополнительно `device_type`, `session_id`, `memory` |
|
||||
| `set-rs485`, `stm32-uart` | `baudrate`, `base_address` |
|
||||
| `f28335-eeprom`, `f28335-flash-rs` | `baudrate`, `controller`, `timeout_ms` |
|
||||
| `stm32-usb` | `base_address` |
|
||||
| `manual`, `f28335-jtag` | Пустой объект `{}` |
|
||||
|
||||
| Поле | Единицы и ограничения |
|
||||
|---|---|
|
||||
| `bitrate` | Целое число бит/с, `1..1000000`; верхняя граница Classic CAN. Например, `250000` — 250 кбит/с. Адаптер также должен поддерживать выбранную скорость. |
|
||||
| `baudrate` | Целое число бод, `1..2147483647`; фактическую поддержку проверяет COM-драйвер. Например, `115200`. |
|
||||
| `node_id` | Адрес SETP `0..255`; для ProtoCAN — поле Device `0..15`. |
|
||||
| `device_type`, `session_id` | Поля существующего ProtoCAN-загрузчика: Type `0..7`, Session ID `1..255`. |
|
||||
| `product_type` | Код совместимости `0..65535`; например, `358` соответствует `0x0166`, а `10291` — `0x2833`. |
|
||||
| `hardware_min`, `hardware_max` | Включительный диапазон аппаратных ревизий `0..255`; min не больше max. |
|
||||
| `base_address` | Целочисленный адрес приложения в **байтах**, `0..4294967295`. Например, `134230016` соответствует `0x08003000`; это адрес примера, а не общий адрес всех STM32. Для HEX должен совпадать с адресом самого образа. |
|
||||
| `controller` | Независимый адрес контроллера RS-протокола ПМ35, `0..255`. |
|
||||
| `timeout_ms` | Дополнительный тайм-аут RS-панели ПМ35 в миллисекундах, `50..10000`. |
|
||||
| `memory` | Выбор назначения ProtoCAN: `eeprom`, `flash` или `stm32`. |
|
||||
|
||||
Формат UART определяется протоколом: SET RS использует 8N1, STM32 ROM Boot —
|
||||
8E1. COM-порт, экземпляр CAN-адаптера и его соединение выбираются на компьютере
|
||||
оператора, поскольку они не являются свойствами образа прошивки.
|
||||
|
||||
Обновлённый SETGUI выбирает загрузчик и заполняет форму из manifest при импорте
|
||||
postbuild и выборе опубликованного комплекта. Общая вкладка прошивки также
|
||||
подхватывает настройки при обычном выборе локального BIN/HEX, если рядом лежит
|
||||
соответствующий `имя.bin.manifest.json` или `имя.hex.manifest.json`; перед
|
||||
применением проверяются имя, размер и SHA-256 файла. Маршрут выбранной записи
|
||||
каталога использует свой профиль, даже если `default_loader` задаёт другой.
|
||||
При переключении общей формы между CAN и RS применяется профиль нового режима.
|
||||
Локальный выбор BIN в панели F28335 подхватывает профиль выбранного RS-загрузчика
|
||||
после штатной проверки образа; JTAG сохраняет отдельное подключение.
|
||||
Скорость уже открытого канала применяется после его отключения. Запись платы
|
||||
запускается отдельной штатной кнопкой.
|
||||
|
||||
Для отдельных CAN BIN и Flash RS BIN C2000 назначение из паспорта артефакта
|
||||
имеет приоритет над `default_loader` основного EEPROM-образа. Профили при этом
|
||||
остаются общими для комплекта. Старые конфигурации без `flashing` сохраняют
|
||||
прежнее поведение; существующие проектные JSON автоматически не заменяются.
|
||||
|
||||
Полные примеры: [Keil](keil/firmware_identity.example.json) и
|
||||
[CCS](ccs/firmware_identity.example.json). Поддержка чтения профилей должна быть
|
||||
включена в сборку SETGUI вместе с обновлённым модулем
|
||||
`set_devices.firmware_flashing` из templates.
|
||||
|
||||
Для проектов ПЛИС используйте [шаблон Xilinx ISE, Vivado и Altera/Intel Quartus](fpga/README.md):
|
||||
общий сохраняемый итератор, паспорт на Verilog/VHDL, полная сборка `.bit`/`.sof`,
|
||||
manifest, SHA-256 и архив выпуска.
|
||||
|
||||
Reference in New Issue
Block a user