Расширить DEIF, загрузку CAN и общую обработку сигналов

Добавить общее ядро DEIF, эмуляцию приборов и переносимые панели Python/Qt.
Расширить загрузчики F2812 и F28335, обновление Flash и профиль STM Invertor.
Добавить альтернативный МНК Eigen, модели неисправностей и диагностику сигналов.
Обновить справочники устройств, проекты CCS и регрессионные проверки.
This commit is contained in:
2026-10-24 15:40:51 +03:00
parent a5e46d4627
commit 41e24629ce
170 changed files with 43762 additions and 281 deletions
@@ -0,0 +1,15 @@
# F2812 / AM29LV800BT
Самостоятельный проект, карта NOR, параметры CAN, установка и ограничения
описаны в [общей инструкции CCS](../../projects/README.md).
- `boot2812.h` — физические CPU word-адреса и отдельная совместимость F2812;
- `nor_flash.c` — x16 AMD driver с ID, ограниченными program/sector erase,
DQ7/DQ5 polling, readback и BUSY;
- `boot_store.c` — упаковка октетов, проверка всей SCI8-таблицы, CRC32,
публикация manifest последними magic-словами, проверенная загрузка RAM;
- `projects/ccs/f2812/board.c` — подтверждённые линии CPLD и XINTF ПМ67;
- `projects/ccs/f2812/main.c` — независимые eCAN/IRQ/очередь/main F2812.
Протокол и CRC повторно используются из `src/pcan_boot.c`. EEPROM отсутствует;
код F28335 и его A/B-журнал не подключаются к этому проекту.
@@ -0,0 +1,24 @@
#ifndef BOOT2812_H /* Исключает повторное объявление профиля NOR-загрузчика F2812. */
#define BOOT2812_H /* Отделяет основной контроллер ПМ67 от периферийного F28335. */
#include "../../include/pcan_boot.h" /* Использует существующий ProtoCAN codec и представление октетов C28x. */
#define BOOT2812_NOR_BASE 0x100000UL /* CPU word-адрес AM29LV800BT взят из START_ADR_FLASH действующего flash_tools.h платы. */
#define BOOT2812_IMAGE_WORD 0x160000UL /* Новый CAN-образ занимает три полных верхних обычных сектора, выше старого образа и данных FPGA. */
#define BOOT2812_IMAGE_BYTES 0x30000UL /* Три сектора по 0x8000 слов дают 196608 октетов полезного SCI8. */
#define BOOT2812_META_WORD 0x17E000UL /* Последний top-boot сектор начинается после областей 16K/4K/4K слов и содержит 8192 слова. */
#define BOOT2812_PRODUCT 0x2812U /* Отдельный Product Type запрещает запись образа F28335 в основной контроллер F2812. */
#define BOOT2812_DEVICE_TYPE 3U /* Тип загрузчика остаётся в том же адресном классе ProtoCAN, что F28335. */
#define BOOT2812_DEVICE_ID 6U /* Отдельный ID 6 исключает совпадение с существующим F28335 на адресе 3/7. */
#define BOOT2812_RECOVERY_MS 3000UL /* После reset загрузчик три секунды принимает ENTER_BOOT до автоматического запуска сохранённого образа. */
unsigned long Boot2812_Now(void); /* Возвращает монотонные миллисекунды Timer1 для CAN и ограниченных ожиданий NOR. */
void Boot2812_Busy(void); /* Во время долгого стирания сообщает BUSY через общий codec и обслуживает аппаратный TX ACK. */
void Boot2812_BoardInit(void); /* Настраивает действующие линии CPLD и XINTF платы ПМ67. */
bool Boot2812_NorIdentify(void); /* До первого erase требует AMD manufacturer 0001 и top-boot device 22DA. */
unsigned int Boot2812_NorRead(unsigned long word); /* Читает одно 16-битное слово по CPU-адресу внешней NOR. */
bool Boot2812_NorProgram(unsigned long word, unsigned int value); /* Программирует только зарезервированные CAN-области и проверяет прочитанный результат. */
bool Boot2812_NorErase(unsigned long word); /* Принимает только начала трёх секторов образа и одного сектора метаданных. */
bool Boot2812_Erase(void *user, pcan_boot_byte_t slot); /* Удаляет публикацию до стирания единственного CAN-слота. */
bool Boot2812_Write(void *user, pcan_boot_byte_t slot, uint32_t offset, const pcan_boot_byte_t *data, pcan_boot_byte_t length); /* Сохраняет октеты ProtoCAN в x16 NOR младшим байтом вперёд. */
pcan_boot_status_t Boot2812_Verify(void *user, pcan_boot_byte_t slot, const pcan_boot_manifest_t *image); /* Проверяет CRC прочитанной NOR и все CPU-адреса SCI8 до COMMIT. */
bool Boot2812_Commit(void *user, pcan_boot_byte_t slot, const pcan_boot_manifest_t *image); /* Публикует manifest только после повторной проверки всей внешней Flash. */
bool Boot2812_Load(uint32_t *entry); /* Читает действующий manifest, проверяет образ и переносит только разрешённую RAM. */
#endif /* Завершает интерфейс самостоятельного NOR-порта F2812. */
@@ -0,0 +1,105 @@
#include "boot2812.h" /* Использует отдельные адреса и совместимость внешней NOR F2812. */
static unsigned int octet(uint32_t offset) /* Читает октет файла SCI8 из x16 NOR, где младший октет хранится первым. */
{ /* Поток CAN остаётся независимым от sizeof(char) на C28x. */
unsigned int word = Boot2812_NorRead(BOOT2812_IMAGE_WORD + offset / 2UL); /* Преобразует смещение в октетах в CPU word-адрес слота. */
return (offset & 1UL) ? word >> 8 : word & 255U; /* Передаёт младшие восемь бит октета без знакового расширения. */
} /* Все дальнейшие поля таблицы собираются из того же порядка октетов. */
static unsigned int u16(uint32_t offset) /* Восстанавливает слово TI boot table в little endian. */
{ /* Вызывается только после проверки достаточной длины соответствующего поля. */
return octet(offset) | (octet(offset + 1UL) << 8); /* Два октета соответствуют одному слову C28x. */
} /* Завершает чтение длины секции или данных программы. */
static uint32_t address_at(uint32_t offset) /* Адрес TI SCI8 содержит старшее 16-битное слово перед младшим. */
{ /* Этот порядок отличается от обычного u32 little endian метаданных ProtoCAN. */
return ((uint32_t)u16(offset) << 16) | u16(offset + 2UL); /* Внутри каждого слова по-прежнему сохраняется little endian. */
} /* Возвращает CPU-адрес, измеряемый в словах. */
static bool allowed(uint32_t first, uint32_t end) /* Разрешает только реальную RAM приложения и исключает RAM работающего загрузчика. */
{ /* Новое приложение не должно загружать инициализированные секции в L1 или RAM InitFlash до перехода в runtime. */
return end > first && ((first >= 0x8000UL && end <= 0x8F00UL) || (first >= 0x3F8000UL && end <= 0x3FA000UL) || (first >= 0x80000UL && end <= 0x100000UL)); /* L0 до ramfuncs, полный H0 и внешняя SRAM зоны 2; периферия, NOR, стек и внутренние Flash запрещены. */
} /* Проверки входа и всех секций выполняются до первой записи приложения в RAM. */
static bool table(uint32_t size, bool copy, uint32_t *entry) /* Проверяет целиком SCI8 и при повторном проходе переносит данные в RAM. */
{ /* Устойчивость загрузчика не зависит от адресов, заявленных принимаемым файлом. */
uint32_t starts[64], ends[64], offset = 22UL, address, end; /* Лимит 64 секции ограничивает стек и число сравнений перекрытий. */
unsigned int sections = 0U, count, i; bool entry_loaded = false; /* Требует, чтобы точка входа реально находилась среди загружаемых слов. */
if (size < 24UL || (size & 1UL) || u16(0UL) != 0x08AAU) return false; /* Ключ SCI8 AA08, чётный размер и терминатор обязательны. */
*entry = address_at(18UL); /* После ключа и восьми резервных слов находится CPU word-адрес C runtime. */
while (offset + 2UL <= size) { /* Поле count читается только при наличии обоих октетов. */
count = u16(offset); offset += 2UL; /* Размер секции выражен в 16-битных словах, а не октетах файла. */
if (!count) return offset == size && entry_loaded; /* Запрещает хвост, незавершённую таблицу и entry вне payload. */
if (sections == 64U || size - offset < 4UL) return false; /* Не допускает переполнения ограниченных массивов или усечённого адреса. */
address = address_at(offset); offset += 4UL; end = address + count; /* Восстанавливает исключающую верхнюю границу CPU-диапазона секции. */
if (!allowed(address, end) || (uint32_t)count * 2UL > size - offset) return false; /* Проверяет адреса и полный payload до обращения к RAM назначения. */
for (i = 0; i < sections; ++i) if (address < ends[i] && starts[i] < end) return false; /* Даже правильно переданная CRC не разрешает две секции поверх одного слова. */
starts[sections] = address; ends[sections++] = end; /* Сохраняет уже проверенный диапазон для следующих секций. */
if (*entry >= address && *entry < end) entry_loaded = true; /* Подтверждает наличие исполняемого слова по адресу входа. */
if (copy) { /* Этот проход выполняется только после полной read-only проверки всего образа. */
for (i = 0; i < count; ++i) { /* Переносит ровно заявленные и предварительно проверенные слова секции. */
#if defined(BOOT2812_HOST) /* Аппаратная модель проверяет тот же диапазон без записи по адресам процесса ПК. */
extern void NorModel_LoadWord(uint32_t target, unsigned int value); /* Модель сохраняет каждое слово переноса для независимой проверки. */
NorModel_LoadWord(address + i, u16(offset + (uint32_t)i * 2UL)); /* Подменяет только шину RAM назначения. */
#else /* На F2812 CPU-адреса уже выражены в 16-битных словах. */
*(volatile unsigned int *)(address + i) = u16(offset + (uint32_t)i * 2UL); /* Записывает секцию только в разрешённую RAM приложения. */
#endif /* Завершает аппаратную запись либо её host-модель. */
} /* Завершает перенос текущей секции. */
} /* После проверочного прохода RAM остаётся нетронутой. */
offset += (uint32_t)count * 2UL; /* Пропускает полезные октеты до следующего count. */
} /* Исчерпание файла без терминатора означает неполный образ. */
return false; /* Не запускает незавершённую таблицу. */
} /* Образ с ошибкой не публикуется как запускаемый. */
bool Boot2812_Erase(void *user, pcan_boot_byte_t slot) /* Реализует single-slot подготовку внешней NOR. */
{ /* Существующие legacy-образы и FPGA ниже 160000 не входят в новый CAN-раздел. */
unsigned long sector; (void)user; /* Перебирает только три полных обычных сектора нового образа. */
if (slot || !Boot2812_NorIdentify() || !Boot2812_NorErase(BOOT2812_META_WORD)) return false; /* Сначала удаляет marker старого CAN-образа; при обрыве стирания сохраняется recovery. */
for (sector = BOOT2812_IMAGE_WORD; sector < BOOT2812_IMAGE_WORD + BOOT2812_IMAGE_BYTES / 2UL; sector += 0x8000UL) if (!Boot2812_NorErase(sector)) return false; /* Геометрия x16 допускает erase только по границам обычных секторов 32K слов. */
return true; /* Ядро ProtoCAN разрешает DATA только после полного успешного стирания слота. */
} /* EEPROM-операции и несуществующий второй слот отсутствуют. */
bool Boot2812_Write(void *user, pcan_boot_byte_t slot, uint32_t offset, const pcan_boot_byte_t *data, pcan_boot_byte_t length) /* Упаковывает поступившие октеты в x16 слова NOR. */
{ /* В одном вызове записывается не более одного classic CAN кадра. */
unsigned int i, word; (void)user; /* Счётчик относится к октетам кадра, адрес NOR отдельно делится на два. */
if (slot || !length || length > 8U || (offset & 1UL) || offset > BOOT2812_IMAGE_BYTES || length > BOOT2812_IMAGE_BYTES - offset) return false; /* Не допускает выхода из слота либо продолжения слова с неверного октета. */
for (i = 0; i < length; i += 2U) { /* Последовательные 8-октетные блоки начинаются на границе x16 слова. */
word = (data[i] & 255U) | ((i + 1U < length ? data[i + 1U] & 255U : 255U) << 8); /* Неполное последнее слово сохраняет FF в незанятом старшем октете. */
if (!Boot2812_NorProgram(BOOT2812_IMAGE_WORD + (offset + i) / 2UL, word)) return false; /* Аппаратный драйвер проверяет и допустимый адрес, и результат записи каждого слова. */
} /* Завершает сохранение текущего CAN-блока. */
return true; /* ACK означает проверенную запись, а не только получение кадра. */
} /* CRC ядра продвигается только после успешного возврата этого callback. */
pcan_boot_status_t Boot2812_Verify(void *user, pcan_boot_byte_t slot, const pcan_boot_manifest_t *image) /* Перепроверяет CRC непосредственно из сохранённой NOR. */
{ /* Потоковая CRC при приёме не заменяет аппаратный readback полного файла. */
uint32_t offset, crc = 0UL, entry; pcan_boot_byte_t bytes[32]; unsigned int count, i; (void)user; /* Небольшой буфер не требует размещать весь образ во внутренней RAM F2812. */
if (slot || image->image_size < 24UL || image->image_size > BOOT2812_IMAGE_BYTES || (image->image_size & 1UL)) return PCAN_BOOT_STATUS_INVALID_SIZE; /* SCI8 должен целиком помещаться в выделенных трёх секторах и содержать целые слова. */
if (image->product_type != BOOT2812_PRODUCT || image->hardware_revision_min > 1U || image->hardware_revision_max < 1U) return PCAN_BOOT_STATUS_WRONG_DEVICE; /* Образ F28335 или несовместимого изделия не становится загрузочным. */
for (offset = 0; offset < image->image_size; offset += count) { /* Проверяет каждый фактически сохранённый октет ровно один раз. */
count = image->image_size - offset < 32UL ? (unsigned int)(image->image_size - offset) : 32U; /* Последний буфер ограничен реальной длиной без FF-дополнения CAN. */
for (i = 0; i < count; ++i) bytes[i] = octet(offset + i); /* Читает данные из NOR, а не из приёмной очереди CAN. */
crc = pcan_boot_crc32_update(crc, bytes, count); /* Использует общий CRC32 ProtoCAN без дублирования полинома в аппаратном порту. */
Boot2812_Busy(); /* Большой файл сохраняет ответы клиента при длинной проверке на реальном CPU. */
} /* Завершает вычисление CRC readback. */
if (crc != image->image_crc32) return PCAN_BOOT_STATUS_CRC_ERROR; /* Различает повреждение памяти и ошибку структуры SCI8. */
return table(image->image_size, false, &entry) ? PCAN_BOOT_STATUS_OK : PCAN_BOOT_STATUS_INVALID_STATE; /* Все адреса, перекрытия, терминатор и entry проверяются до COMMIT. */
} /* До успеха verifier никакие слова приложения не переносятся в RAM. */
bool Boot2812_Commit(void *user, pcan_boot_byte_t slot, const pcan_boot_manifest_t *image) /* Публикует загрузочный manifest последней операцией после readback. */
{ /* Формат из 12 слов фиксирован и не зависит от sizeof C-структуры. */
unsigned int words[12], i; /* Два magic-слова, версия формата, размер, CRC, версия FW и контроль manifest. */
pcan_boot_byte_t bytes[20]; uint32_t crc; /* CRC первых десяти слов защищает размер и контрольную сумму самого образа. */
if (Boot2812_Verify(user, slot, image) != PCAN_BOOT_STATUS_OK) return false; /* Не публикует файл, испорченный после предыдущего VERIFY. */
words[0] = 0x2812U; words[1] = 0xCA7EU; words[2] = 1U; words[3] = 0U; /* Magic 2812/CA7E и версия 1 отличают новый CAN manifest от прежних данных платы. */
words[4] = (unsigned int)(image->image_size & 0xFFFFUL); words[5] = (unsigned int)(image->image_size >> 16); /* Длина выражена октетами и хранится младшим словом первой. */
words[6] = (unsigned int)(image->image_crc32 & 0xFFFFUL); words[7] = (unsigned int)(image->image_crc32 >> 16); /* Сохраняет точную CRC всего полученного файла. */
words[8] = (unsigned int)(image->firmware_version & 0xFFFFUL); words[9] = (unsigned int)(image->firmware_version >> 16); /* Версия кандидата не подменяется версией постоянного загрузчика. */
for (i = 0; i < 10U; ++i) { bytes[2U * i] = words[i] & 255U; bytes[2U * i + 1U] = words[i] >> 8; } /* Wire-представление manifest использует октеты независимо от ширины C28x char. */
crc = pcan_boot_crc32_update(0UL, bytes, 20U); words[10] = (unsigned int)(crc & 0xFFFFUL); words[11] = (unsigned int)(crc >> 16); /* CRC manifest не позволяет принять частично записанные поля размера и CRC образа. */
for (i = 2U; i < 12U; ++i) if (!Boot2812_NorProgram(BOOT2812_META_WORD + i, words[i])) return false; /* Сначала записывает все поля, пока оба magic-слова ещё равны FF. */
if (!Boot2812_NorProgram(BOOT2812_META_WORD + 1UL, words[1])) return false; /* Второе magic-слово фиксируется после полного тела manifest. */
return Boot2812_NorProgram(BOOT2812_META_WORD, words[0]); /* Первое magic записывается последним: потеря питания раньше оставляет recovery. */
} /* Повтор COMMIT обрабатывает идемпотентный автомат общего ядра. */
bool Boot2812_Load(uint32_t *entry) /* Проверяет опубликованный образ после reset перед переносом в RAM приложения. */
{ /* Пустой или повреждённый manifest оставляет прибор в постоянном CAN recovery. */
unsigned int words[12], i; pcan_boot_byte_t bytes[20]; uint32_t crc; pcan_boot_manifest_t image; /* Сериализованные поля читаются явно, без memcpy по ABI структуры. */
for (i = 0; i < 12U; ++i) words[i] = Boot2812_NorRead(BOOT2812_META_WORD + i); /* Читает только фиксированные 24 октета manifest. */
if (words[0] != 0x2812U || words[1] != 0xCA7EU || words[2] != 1U || words[3]) return false; /* Не интерпретирует старые данные Flash либо неполный commit как запускаемый файл. */
for (i = 0; i < 10U; ++i) { bytes[2U * i] = words[i] & 255U; bytes[2U * i + 1U] = words[i] >> 8; } /* Восстанавливает то же октетное представление, что защищалось при COMMIT. */
crc = pcan_boot_crc32_update(0UL, bytes, 20U); /* Проверяет метаданные до использования объявленной ими длины. */
if (crc != ((uint32_t)words[10] | ((uint32_t)words[11] << 16))) return false; /* Повреждённый размер не может вывести чтение за пределы CAN-слота. */
image.image_size = (uint32_t)words[4] | ((uint32_t)words[5] << 16); image.image_crc32 = (uint32_t)words[6] | ((uint32_t)words[7] << 16); /* Размер и CRC файла восстанавливаются только после успешной CRC manifest. */
image.firmware_version = (uint32_t)words[8] | ((uint32_t)words[9] << 16); image.product_type = BOOT2812_PRODUCT; image.hardware_revision_min = image.hardware_revision_max = 1U; /* Профиль образа был проверен до публикации manifest. */
return Boot2812_Verify(0, 0U, &image) == PCAN_BOOT_STATUS_OK && table(image.image_size, true, entry); /* Сначала полный read-only проход, затем перенос и только после успеха разрешение вызова entry. */
} /* Не загружает старые flat BIN автоматически: новый проект использует проверяемую SCI8-таблицу. */
@@ -0,0 +1,69 @@
#include "boot2812.h" /* Ограничивает аппаратный драйвер разделами обновления конкретного профиля ПМ67. */
#if defined(BOOT2812_HOST) /* Host-модель воспроизводит команды NOR без обращения к физическим CPU-адресам. */
extern unsigned int NorModel_Read(unsigned long word); /* Модель отдаёт данные, ID и аппаратные биты готовности чипа. */
extern void NorModel_Write(unsigned long word, unsigned int value); /* Модель принимает настоящую последовательность unlock/program/erase драйвера. */
#define NOR_READ(word) NorModel_Read(word) /* Подменяет только чтение шины, сохраняя алгоритм драйвера. */
#define NOR_WRITE(word, value) NorModel_Write(word, value) /* Подменяет физическую запись при проверке границ и отказов. */
#else /* Production использует 16-битную параллельную XINTF-шину. */
#define NOR_READ(word) (*(volatile unsigned int *)(word)) /* Один CPU-адрес C28x соответствует одному x16 слову AM29LV800BT. */
#define NOR_WRITE(word, value) (*(volatile unsigned int *)(word) = (value)) /* Volatile сохраняет каждый командный цикл AMD в исходном порядке. */
#endif /* Завершает выбор физической шины либо модели. */
static void reset_read(void) /* Выводит чип из autoselect либо неудачной операции в обычный режим чтения. */
{ /* Reset не стирает слова памяти и допустим по любому адресу чипа. */
NOR_WRITE(BOOT2812_NOR_BASE, 0x00F0U); /* F0 — Read/Reset из таблицы команд AMD Am29LV800B. */
} /* Все ошибки возвращают NOR в состояние, пригодное для следующей recovery-сессии. */
static void unlock(unsigned int command) /* Выполняет общий префикс x16-команд согласно datasheet AMD. */
{ /* Адреса ниже являются смещениями в словах, а не октетными AAA/555 режима x8. */
NOR_WRITE(BOOT2812_NOR_BASE + 0x555UL, 0x00AAU); /* Первый unlock-цикл AA по смещению 555h в режиме x16. */
NOR_WRITE(BOOT2812_NOR_BASE + 0x2AAUL, 0x0055U); /* Второй unlock-цикл 55 по смещению 2AAh. */
NOR_WRITE(BOOT2812_NOR_BASE + 0x555UL, command); /* Третий цикл выбирает autoselect, программирование либо начало стирания. */
} /* Последующий цикл данных или адрес сектора выполняет вызывающая операция. */
unsigned int Boot2812_NorRead(unsigned long word) /* Читает только физический диапазон установленной NOR ёмкостью 1 МиБ. */
{ /* Верхняя исключающая граница равна BASE + 512K слов. */
return word >= BOOT2812_NOR_BASE && word < BOOT2812_NOR_BASE + 0x80000UL ? NOR_READ(word) : 0xFFFFU; /* Вне чипа не создаёт шинного чтения по неизвестному адресу. */
} /* Возвращает полное x16 слово без перестановки октетов. */
bool Boot2812_NorIdentify(void) /* Идентификация обязательна перед разрешением изменяющих операций. */
{ /* Не подменяет top-boot геометрию похожим bottom-boot или NAND-чипом. */
unsigned int maker, device; /* Хранит два слова autoselect, прежде чем вернуть чип к чтению. */
reset_read(); unlock(0x0090U); /* 90h включает чтение manufacturer/device ID после обычного unlock. */
maker = NOR_READ(BOOT2812_NOR_BASE); device = NOR_READ(BOOT2812_NOR_BASE + 1UL); /* AMD: offset 0 содержит 0001, offset 1 — 22DA для Am29LV800BT x16. */
reset_read(); /* Обычные чтения образа после проверки не должны видеть таблицу autoselect. */
return maker == 0x0001U && device == 0x22DAU; /* Отличающийся чип получает отказ до первого sector erase. */
} /* Не выполняет разрушительных пробных записей для определения типа памяти. */
static bool wait_word(unsigned long word, unsigned int expected, unsigned long timeout) /* Ограничивает DQ7/DQ5 polling временем и числом проходов. */
{ /* DQ7 отражает готовность программируемого старшего бита; DQ5 требует завершающего повторного чтения. */
unsigned long started = Boot2812_Now(), budget = 50000000UL; /* Итерационный предел обеспечивает выход даже при остановленном Timer1. */
unsigned int value; /* Снимок status считывается один раз на основной проход. */
do { /* Не вызывает никаких функций из программируемой внешней NOR: код загрузчика находится во внутренней Flash. */
value = NOR_READ(word); /* Читает именно адрес программирования либо начало стираемого сектора. */
if ((value & 0x0080U) == (expected & 0x0080U)) return NOR_READ(word) == expected; /* После совпадения DQ7 подтверждает все 16 бит результата. */
if (value & 0x0020U) { /* DQ5 сообщает превышение внутреннего времени алгоритма AMD. */
if ((NOR_READ(word) & 0x0080U) == (expected & 0x0080U)) return NOR_READ(word) == expected; /* Повтор исключает нормальное завершение операции одновременно с чтением DQ5. */
break; /* После подтверждённой ошибки больше не ждёт неопределённое время. */
} /* Завершает трактовку аппаратного статуса текущего прохода. */
Boot2812_Busy(); /* Долгое стирание продолжает давать клиенту живой ответ и не блокирует CAN ISR. */
} while (--budget && Boot2812_Now() - started < timeout); /* Беззнаковое вычитание сохраняет корректность при переполнении миллисекунд. */
reset_read(); /* Отказ возвращает чип в режим чтения перед следующей recovery-попыткой. */
return false; /* Не сообщает успех по одному окончанию тайм-аута. */
} /* Завершает ограниченное ожидание аппаратной операции. */
bool Boot2812_NorProgram(unsigned long word, unsigned int value) /* Программирует только выделенный CAN-образ либо manifest. */
{ /* Старые данные приложения, FPGA и остальные сектора чипа не входят в разрешённые диапазоны. */
unsigned int before; /* Предварительное чтение исключает попытку перевода NOR-бита из нуля в единицу. */
if (!((word >= BOOT2812_IMAGE_WORD && word < BOOT2812_IMAGE_WORD + BOOT2812_IMAGE_BYTES / 2UL) || (word >= BOOT2812_META_WORD && word < BOOT2812_META_WORD + 0x2000UL))) return false; /* Проверяет word-адрес до любого unlock или командного цикла. */
before = NOR_READ(word); /* Проверяет текущее содержимое перед началом операции. */
if (before == value) return true; /* Повтор одинакового блока не выполняет лишнее программирование. */
if ((before & value) != value) return false; /* Бит 0→1 требует отдельного sector erase и не должен маскироваться чтением старого слова. */
unlock(0x00A0U); NOR_WRITE(word, value); /* A0 запускает word program; следующий цикл содержит адрес и полные 16 бит данных. */
return wait_word(word, value, 100UL); /* Сто миллисекунд дают ограниченный запас для программирования одного слова. */
} /* Каждый успех означает фактическое совпадение прочитанного слова. */
bool Boot2812_NorErase(unsigned long word) /* Не принимает произвольный адрес или команду полного стирания чипа. */
{ /* Обновление ограничено секторами 12/13/14 и последним top-boot сектором 18. */
unsigned long i, count = word == BOOT2812_META_WORD ? 0x2000UL : 0x8000UL; /* AM29LV800BT: обычный сектор 32K слов, последний boot-сектор 8K слов. */
if (!(word == BOOT2812_IMAGE_WORD || word == BOOT2812_IMAGE_WORD + 0x8000UL || word == BOOT2812_IMAGE_WORD + 0x10000UL || word == BOOT2812_META_WORD)) return false; /* Только точные разрешённые начала секторов исключают округление в соседнюю область. */
unlock(0x0080U); /* 80h начинает шеститактовую последовательность Sector Erase AMD. */
NOR_WRITE(BOOT2812_NOR_BASE + 0x555UL, 0x00AAU); NOR_WRITE(BOOT2812_NOR_BASE + 0x2AAUL, 0x0055U); /* Повторяет вторую unlock-пару перед выбором сектора. */
NOR_WRITE(word, 0x0030U); /* 30h по sector address выбирает один сектор; команда Chip Erase 10h отсутствует. */
if (!wait_word(word, 0xFFFFU, 20000UL)) return false; /* Ограничивает ожидание двадцатью секундами с периодическими BUSY-ответами. */
for (i = 0; i < count; ++i) if (NOR_READ(word + i) != 0xFFFFU) return false; /* Проверяет всю стёртую область, а не только её первое слово. */
return true; /* Разрешает запись лишь после полного аппаратного readback сектора. */
} /* При сбое сохранённый manifest уже недействителен, загрузчик остаётся в recovery. */